@stratum-hq/sdk 1.0.1 → 1.2.0
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/README.md +71 -8
- package/dist/client.d.ts +38 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +171 -26
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/express.d.ts.map +1 -1
- package/dist/middleware/express.js +35 -23
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/fastify.d.ts.map +1 -1
- package/dist/middleware/fastify.js +40 -24
- package/dist/middleware/fastify.js.map +1 -1
- package/dist/middleware/tenant-errors.d.ts +27 -0
- package/dist/middleware/tenant-errors.d.ts.map +1 -0
- package/dist/middleware/tenant-errors.js +52 -0
- package/dist/middleware/tenant-errors.js.map +1 -0
- package/dist/resolvers/header.d.ts +5 -1
- package/dist/resolvers/header.d.ts.map +1 -1
- package/dist/resolvers/header.js +8 -2
- package/dist/resolvers/header.js.map +1 -1
- package/dist/resolvers/jwt.d.ts +28 -0
- package/dist/resolvers/jwt.d.ts.map +1 -1
- package/dist/resolvers/jwt.js +46 -9
- package/dist/resolvers/jwt.js.map +1 -1
- package/dist/resolvers/resolve.d.ts +25 -0
- package/dist/resolvers/resolve.d.ts.map +1 -0
- package/dist/resolvers/resolve.js +39 -0
- package/dist/resolvers/resolve.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -4
|
@@ -1,44 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.expressMiddleware = expressMiddleware;
|
|
4
|
-
const core_1 = require("@stratum-hq/core");
|
|
5
4
|
const context_js_1 = require("../context.js");
|
|
6
5
|
const jwt_js_1 = require("../resolvers/jwt.js");
|
|
7
|
-
const
|
|
6
|
+
const resolve_js_1 = require("../resolvers/resolve.js");
|
|
7
|
+
const tenant_errors_js_1 = require("./tenant-errors.js");
|
|
8
8
|
function expressMiddleware(client, options) {
|
|
9
|
+
(0, jwt_js_1.assertJwtSupport)(options);
|
|
9
10
|
return async (req, res, next) => {
|
|
11
|
+
/** Send the response for a known resolution error. Return false for any other error. */
|
|
12
|
+
const answerResolutionError = (err, tenantId) => {
|
|
13
|
+
const response = (0, tenant_errors_js_1.tenantErrorResponse)(err, tenantId);
|
|
14
|
+
if (response) {
|
|
15
|
+
res.status(response.status).json(response.body);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const failure = (0, tenant_errors_js_1.controlPlaneErrorResponse)(err);
|
|
19
|
+
if (!failure)
|
|
20
|
+
return false;
|
|
21
|
+
if (options?.onError && err instanceof Error) {
|
|
22
|
+
options.onError(err, req);
|
|
23
|
+
}
|
|
24
|
+
res.status(failure.status).json(failure.body);
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
10
27
|
try {
|
|
11
28
|
// Resolve tenant ID: JWT → header → custom resolvers
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
if (!tenantId) {
|
|
18
|
-
tenantId = (0, header_js_1.resolveFromHeader)(req);
|
|
19
|
-
}
|
|
20
|
-
if (!tenantId && options?.resolvers) {
|
|
21
|
-
for (const resolver of options.resolvers) {
|
|
22
|
-
const result = await resolver.resolve(req);
|
|
23
|
-
if (result) {
|
|
24
|
-
tenantId = result;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
29
|
+
const resolution = await (0, resolve_js_1.resolveTenantId)(req, options);
|
|
30
|
+
if (resolution.status === "invalid_token") {
|
|
31
|
+
res.status(401).json({ error: { code: "INVALID_TOKEN", message: "Bearer token could not be verified" } });
|
|
32
|
+
return;
|
|
28
33
|
}
|
|
29
|
-
if (
|
|
34
|
+
if (resolution.status === "missing") {
|
|
30
35
|
res.status(400).json({ error: { code: "MISSING_TENANT", message: "Tenant ID could not be resolved from request" } });
|
|
31
36
|
return;
|
|
32
37
|
}
|
|
38
|
+
const tenantId = resolution.tenantId;
|
|
33
39
|
let context;
|
|
34
40
|
try {
|
|
35
41
|
context = await client.resolveTenant(tenantId);
|
|
36
42
|
}
|
|
37
43
|
catch (err) {
|
|
38
|
-
if (err
|
|
39
|
-
res.status(404).json({ error: { code: "TENANT_NOT_FOUND", message: `Tenant not found: ${tenantId}` } });
|
|
44
|
+
if (answerResolutionError(err, tenantId))
|
|
40
45
|
return;
|
|
41
|
-
}
|
|
42
46
|
throw err;
|
|
43
47
|
}
|
|
44
48
|
req.tenant = context;
|
|
@@ -59,7 +63,15 @@ function expressMiddleware(client, options) {
|
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
65
|
// Resolve the impersonated tenant's context
|
|
62
|
-
|
|
66
|
+
let impersonatedContext;
|
|
67
|
+
try {
|
|
68
|
+
impersonatedContext = await client.resolveTenant(impersonateTenantId);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (answerResolutionError(err, impersonateTenantId))
|
|
72
|
+
return;
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
63
75
|
req.tenant = impersonatedContext;
|
|
64
76
|
req.impersonating = true;
|
|
65
77
|
req.originalTenantId = tenantId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":";;AAsBA,
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/middleware/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQrD,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEtC,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,UAAU,gBAAgB;IACxB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;CACrD;AAED,UAAU,WAAW;IACnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACpD,OAAO,CACL,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GACpF,IAAI,CAAC;CACT;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GAAG,iBAAiB,EACtD,IAAI,EAAE,MAAM,GACX,IAAI,CAkGN"}
|
|
@@ -1,48 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fastifyPlugin = fastifyPlugin;
|
|
4
|
-
const core_1 = require("@stratum-hq/core");
|
|
5
4
|
const context_js_1 = require("../context.js");
|
|
6
5
|
const jwt_js_1 = require("../resolvers/jwt.js");
|
|
7
|
-
const
|
|
6
|
+
const resolve_js_1 = require("../resolvers/resolve.js");
|
|
7
|
+
const tenant_errors_js_1 = require("./tenant-errors.js");
|
|
8
8
|
function fastifyPlugin(fastify, options, done) {
|
|
9
9
|
const { client, ...middlewareOptions } = options;
|
|
10
|
+
(0, jwt_js_1.assertJwtSupport)(middlewareOptions);
|
|
10
11
|
fastify.decorateRequest("tenant", null);
|
|
11
12
|
fastify.decorateRequest("impersonating", false);
|
|
12
13
|
fastify.decorateRequest("originalTenantId", null);
|
|
13
14
|
fastify.addHook("onRequest", (request, reply, done) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/** Send the response for a known resolution error. Return false for any other error. */
|
|
16
|
+
const answerResolutionError = (err, tenantId) => {
|
|
17
|
+
const response = (0, tenant_errors_js_1.tenantErrorResponse)(err, tenantId);
|
|
18
|
+
if (response) {
|
|
19
|
+
reply.status(response.status).send(response.body);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const failure = (0, tenant_errors_js_1.controlPlaneErrorResponse)(err);
|
|
23
|
+
if (!failure)
|
|
24
|
+
return false;
|
|
25
|
+
if (middlewareOptions.onError && err instanceof Error) {
|
|
26
|
+
middlewareOptions.onError(err, request);
|
|
27
|
+
}
|
|
28
|
+
reply.status(failure.status).send(failure.body);
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
23
31
|
const resolveAndRun = async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
+
// Resolve tenant ID: JWT → header → custom resolvers
|
|
33
|
+
const resolution = await (0, resolve_js_1.resolveTenantId)(request, middlewareOptions);
|
|
34
|
+
if (resolution.status === "invalid_token") {
|
|
35
|
+
reply.status(401).send({ error: { code: "INVALID_TOKEN", message: "Bearer token could not be verified" } });
|
|
36
|
+
return;
|
|
32
37
|
}
|
|
33
|
-
if (
|
|
38
|
+
if (resolution.status === "missing") {
|
|
34
39
|
reply.status(400).send({ error: { code: "MISSING_TENANT", message: "Tenant ID could not be resolved from request" } });
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
42
|
+
const tenantId = resolution.tenantId;
|
|
37
43
|
let context;
|
|
38
44
|
try {
|
|
39
45
|
context = await client.resolveTenant(tenantId);
|
|
40
46
|
}
|
|
41
47
|
catch (err) {
|
|
42
|
-
if (err
|
|
43
|
-
reply.status(404).send({ error: { code: "TENANT_NOT_FOUND", message: `Tenant not found: ${tenantId}` } });
|
|
48
|
+
if (answerResolutionError(err, tenantId))
|
|
44
49
|
return;
|
|
45
|
-
}
|
|
46
50
|
if (middlewareOptions.onError && err instanceof Error) {
|
|
47
51
|
middlewareOptions.onError(err, request);
|
|
48
52
|
}
|
|
@@ -65,7 +69,15 @@ function fastifyPlugin(fastify, options, done) {
|
|
|
65
69
|
});
|
|
66
70
|
return;
|
|
67
71
|
}
|
|
68
|
-
|
|
72
|
+
let impersonatedContext;
|
|
73
|
+
try {
|
|
74
|
+
impersonatedContext = await client.resolveTenant(impersonateTenantId);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (answerResolutionError(err, impersonateTenantId))
|
|
78
|
+
return;
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
69
81
|
request.tenant = impersonatedContext;
|
|
70
82
|
request.impersonating = true;
|
|
71
83
|
request.originalTenantId = tenantId;
|
|
@@ -81,4 +93,8 @@ function fastifyPlugin(fastify, options, done) {
|
|
|
81
93
|
});
|
|
82
94
|
done();
|
|
83
95
|
}
|
|
96
|
+
// Register the hook and decorators on the instance that calls
|
|
97
|
+
// `register(fastifyPlugin)`, not in an encapsulated child context, so routes
|
|
98
|
+
// declared on that instance run the tenant hook (the fastify-plugin convention).
|
|
99
|
+
fastifyPlugin[Symbol.for("skip-override")] = true;
|
|
84
100
|
//# sourceMappingURL=fastify.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":";;AA+BA,
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/middleware/fastify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface TenantErrorResponse {
|
|
2
|
+
status: number;
|
|
3
|
+
body: {
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return the HTTP response for a tenant resolution error, or null when the
|
|
12
|
+
* error is not a known tenant error. The adapters send this response instead
|
|
13
|
+
* of passing the error on, where it would become a 500. A tenant error is a
|
|
14
|
+
* normal answer for the request, so the adapters do not call `onError` for it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function tenantErrorResponse(err: unknown, tenantId: string): TenantErrorResponse | null;
|
|
17
|
+
/**
|
|
18
|
+
* Return the HTTP response for a control plane failure during tenant
|
|
19
|
+
* resolution, or null for any other error.
|
|
20
|
+
*
|
|
21
|
+
* A timeout answers 504. A rejected SDK API key answers 500, because the
|
|
22
|
+
* fault is in the server configuration and not in the request. For a rejected
|
|
23
|
+
* key, this function also writes the cause to `console.error`, because the
|
|
24
|
+
* response body does not show it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function controlPlaneErrorResponse(err: unknown): TenantErrorResponse | null;
|
|
27
|
+
//# sourceMappingURL=tenant-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-errors.d.ts","sourceRoot":"","sources":["../../src/middleware/tenant-errors.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACpD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAc9F;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAiBlF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tenantErrorResponse = tenantErrorResponse;
|
|
4
|
+
exports.controlPlaneErrorResponse = controlPlaneErrorResponse;
|
|
5
|
+
const core_1 = require("@stratum-hq/core");
|
|
6
|
+
/**
|
|
7
|
+
* Return the HTTP response for a tenant resolution error, or null when the
|
|
8
|
+
* error is not a known tenant error. The adapters send this response instead
|
|
9
|
+
* of passing the error on, where it would become a 500. A tenant error is a
|
|
10
|
+
* normal answer for the request, so the adapters do not call `onError` for it.
|
|
11
|
+
*/
|
|
12
|
+
function tenantErrorResponse(err, tenantId) {
|
|
13
|
+
if (err instanceof core_1.TenantNotFoundError) {
|
|
14
|
+
return { status: 404, body: { error: { code: "TENANT_NOT_FOUND", message: `Tenant not found: ${tenantId}` } } };
|
|
15
|
+
}
|
|
16
|
+
if (err instanceof core_1.TenantSuspendedError) {
|
|
17
|
+
return { status: 403, body: { error: { code: "TENANT_SUSPENDED", message: `Tenant ${tenantId} is suspended` } } };
|
|
18
|
+
}
|
|
19
|
+
if (err instanceof core_1.TenantArchivedError) {
|
|
20
|
+
return { status: 410, body: { error: { code: "TENANT_ARCHIVED", message: `Tenant ${tenantId} is archived` } } };
|
|
21
|
+
}
|
|
22
|
+
if (err instanceof core_1.ForbiddenError) {
|
|
23
|
+
return { status: 403, body: { error: { code: "FORBIDDEN", message: `Access to tenant ${tenantId} is denied` } } };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Return the HTTP response for a control plane failure during tenant
|
|
29
|
+
* resolution, or null for any other error.
|
|
30
|
+
*
|
|
31
|
+
* A timeout answers 504. A rejected SDK API key answers 500, because the
|
|
32
|
+
* fault is in the server configuration and not in the request. For a rejected
|
|
33
|
+
* key, this function also writes the cause to `console.error`, because the
|
|
34
|
+
* response body does not show it.
|
|
35
|
+
*/
|
|
36
|
+
function controlPlaneErrorResponse(err) {
|
|
37
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
38
|
+
return {
|
|
39
|
+
status: 504,
|
|
40
|
+
body: { error: { code: "CONTROL_PLANE_TIMEOUT", message: "The Stratum control plane did not respond in time" } },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (err instanceof core_1.UnauthorizedError) {
|
|
44
|
+
console.error("[stratum] The control plane rejected the SDK API key (401). Check the apiKey option of StratumClient.");
|
|
45
|
+
return {
|
|
46
|
+
status: 500,
|
|
47
|
+
body: { error: { code: "CONTROL_PLANE_AUTH_FAILED", message: "Tenant resolution failed" } },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=tenant-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-errors.js","sourceRoot":"","sources":["../../src/middleware/tenant-errors.ts"],"names":[],"mappings":";;AAmBA,kDAcC;AAWD,8DAiBC;AA7DD,2CAM0B;AAO1B;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,GAAY,EAAE,QAAgB;IAChE,IAAI,GAAG,YAAY,0BAAmB,EAAE,CAAC;QACvC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,qBAAqB,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC;IAClH,CAAC;IACD,IAAI,GAAG,YAAY,2BAAoB,EAAE,CAAC;QACxC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,UAAU,QAAQ,eAAe,EAAE,EAAE,EAAE,CAAC;IACpH,CAAC;IACD,IAAI,GAAG,YAAY,0BAAmB,EAAE,CAAC;QACvC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,QAAQ,cAAc,EAAE,EAAE,EAAE,CAAC;IAClH,CAAC;IACD,IAAI,GAAG,YAAY,qBAAc,EAAE,CAAC;QAClC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,QAAQ,YAAY,EAAE,EAAE,EAAE,CAAC;IACpH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,yBAAyB,CAAC,GAAY;IACpD,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QACxD,OAAO;YACL,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,mDAAmD,EAAE,EAAE;SACjH,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,YAAY,wBAAiB,EAAE,CAAC;QACrC,OAAO,CAAC,KAAK,CACX,uGAAuG,CACxG,CAAC;QACF,OAAO;YACL,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,0BAA0B,EAAE,EAAE;SAC5F,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Read the tenant ID from a request header. When `headerName` is given, only
|
|
3
|
+
* that header is read; otherwise the default X-Tenant-ID header is used.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveFromHeader(req: unknown, headerName?: string): string | null;
|
|
2
6
|
//# sourceMappingURL=header.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../src/resolvers/header.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../src/resolvers/header.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASlF"}
|
package/dist/resolvers/header.js
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveFromHeader = resolveFromHeader;
|
|
4
4
|
const core_1 = require("@stratum-hq/core");
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Read the tenant ID from a request header. When `headerName` is given, only
|
|
7
|
+
* that header is read; otherwise the default X-Tenant-ID header is used.
|
|
8
|
+
*/
|
|
9
|
+
function resolveFromHeader(req, headerName) {
|
|
6
10
|
const r = req;
|
|
7
11
|
const headers = r["headers"];
|
|
8
12
|
if (!headers)
|
|
9
13
|
return null;
|
|
10
|
-
const value =
|
|
14
|
+
const value = headerName
|
|
15
|
+
? headers[headerName.toLowerCase()]
|
|
16
|
+
: headers[core_1.TENANT_HEADER.toLowerCase()] ?? headers[core_1.TENANT_HEADER];
|
|
11
17
|
if (!value)
|
|
12
18
|
return null;
|
|
13
19
|
return Array.isArray(value) ? value[0] ?? null : value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/resolvers/header.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/resolvers/header.ts"],"names":[],"mappings":";;AAMA,8CASC;AAfD,2CAAiD;AAEjD;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,UAAmB;IACjE,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,CAA8D,CAAC;IAC1F,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,KAAK,GAAG,UAAU;QACtB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QACnC,CAAC,CAAC,OAAO,CAAC,oBAAa,CAAC,WAAW,EAAE,CAAC,IAAI,OAAO,CAAC,oBAAa,CAAC,CAAC;IACnE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC"}
|
package/dist/resolvers/jwt.d.ts
CHANGED
|
@@ -2,5 +2,33 @@ export interface JwtResolverOptions {
|
|
|
2
2
|
secret?: string;
|
|
3
3
|
verify?: (token: string) => Record<string, unknown> | null;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Throw if `jwtSecret` is configured without a `jwtVerify` function and the
|
|
7
|
+
* optional `jsonwebtoken` peer dependency cannot be loaded. Called when a
|
|
8
|
+
* middleware is constructed, so a missing module fails loudly at startup
|
|
9
|
+
* instead of making every token look unverifiable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertJwtSupport(options?: {
|
|
12
|
+
jwtSecret?: string;
|
|
13
|
+
jwtVerify?: unknown;
|
|
14
|
+
}): void;
|
|
15
|
+
/**
|
|
16
|
+
* Outcome of JWT tenant resolution:
|
|
17
|
+
* - `absent`: no bearer token, or no secret / verify function configured
|
|
18
|
+
* - `invalid`: a bearer token was sent but did not verify
|
|
19
|
+
* - `no_claim`: the token verified but carries no string tenant claim
|
|
20
|
+
* - `resolved`: the token verified and carries the tenant claim
|
|
21
|
+
*/
|
|
22
|
+
export type JwtTenantResolution = {
|
|
23
|
+
status: "absent";
|
|
24
|
+
} | {
|
|
25
|
+
status: "invalid";
|
|
26
|
+
} | {
|
|
27
|
+
status: "no_claim";
|
|
28
|
+
} | {
|
|
29
|
+
status: "resolved";
|
|
30
|
+
tenantId: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function resolveJwtTenant(req: unknown, claimPath?: string, options?: JwtResolverOptions): JwtTenantResolution;
|
|
5
33
|
export declare function resolveFromJwt(req: unknown, claimPath?: string, options?: JwtResolverOptions): string | null;
|
|
6
34
|
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/resolvers/jwt.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5D;
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/resolvers/jwt.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC5D;AAkBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAQ5F;AAgBD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,MAAoB,EAC/B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,mBAAmB,CAuCrB;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,MAAoB,EAC/B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,MAAM,GAAG,IAAI,CAGf"}
|
package/dist/resolvers/jwt.js
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertJwtSupport = assertJwtSupport;
|
|
4
|
+
exports.resolveJwtTenant = resolveJwtTenant;
|
|
3
5
|
exports.resolveFromJwt = resolveFromJwt;
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Load the optional `jsonwebtoken` peer dependency. Returns null only when the
|
|
8
|
+
* module is not installed; any other load failure is rethrown.
|
|
9
|
+
*/
|
|
10
|
+
function loadJsonwebtoken() {
|
|
5
11
|
try {
|
|
6
12
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
-
|
|
13
|
+
return require("jsonwebtoken");
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err.code === "MODULE_NOT_FOUND")
|
|
17
|
+
return null;
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Throw if `jwtSecret` is configured without a `jwtVerify` function and the
|
|
23
|
+
* optional `jsonwebtoken` peer dependency cannot be loaded. Called when a
|
|
24
|
+
* middleware is constructed, so a missing module fails loudly at startup
|
|
25
|
+
* instead of making every token look unverifiable.
|
|
26
|
+
*/
|
|
27
|
+
function assertJwtSupport(options) {
|
|
28
|
+
if (!options?.jwtSecret || options.jwtVerify)
|
|
29
|
+
return;
|
|
30
|
+
if (!loadJsonwebtoken()) {
|
|
31
|
+
throw new Error("[stratum] jwtSecret is set but the optional peer dependency 'jsonwebtoken' could not be loaded. " +
|
|
32
|
+
"Install jsonwebtoken, or provide jwtVerify.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function verifyWithJsonwebtoken(token, secret) {
|
|
36
|
+
const jwt = loadJsonwebtoken();
|
|
37
|
+
if (!jwt) {
|
|
38
|
+
throw new Error("[stratum] jwtSecret is set but the optional peer dependency 'jsonwebtoken' is not installed.");
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
8
41
|
const decoded = jwt.verify(token, secret, { algorithms: ["HS256"] });
|
|
9
42
|
if (typeof decoded === "string")
|
|
10
43
|
return null;
|
|
@@ -14,15 +47,15 @@ function verifyWithJsonwebtoken(token, secret) {
|
|
|
14
47
|
return null;
|
|
15
48
|
}
|
|
16
49
|
}
|
|
17
|
-
function
|
|
50
|
+
function resolveJwtTenant(req, claimPath = "tenant_id", options) {
|
|
18
51
|
const r = req;
|
|
19
52
|
const headers = r["headers"];
|
|
20
53
|
if (!headers)
|
|
21
|
-
return
|
|
54
|
+
return { status: "absent" };
|
|
22
55
|
const authHeader = headers["authorization"];
|
|
23
56
|
const auth = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
24
57
|
if (!auth || !auth.startsWith("Bearer "))
|
|
25
|
-
return
|
|
58
|
+
return { status: "absent" };
|
|
26
59
|
const token = auth.slice(7);
|
|
27
60
|
let claims = null;
|
|
28
61
|
if (options?.verify) {
|
|
@@ -37,18 +70,22 @@ function resolveFromJwt(req, claimPath = "tenant_id", options) {
|
|
|
37
70
|
// No secret or verify function provided — refuse to trust unsigned tokens
|
|
38
71
|
console.warn("[stratum] JWT ignored: no jwtSecret or jwtVerify provided. " +
|
|
39
72
|
"Configure middleware options to enable JWT tenant resolution.");
|
|
40
|
-
return
|
|
73
|
+
return { status: "absent" };
|
|
41
74
|
}
|
|
42
75
|
if (!claims)
|
|
43
|
-
return
|
|
76
|
+
return { status: "invalid" };
|
|
44
77
|
// Support dotted claim paths like "stratum.tenant_id"
|
|
45
78
|
const segments = claimPath.split(".");
|
|
46
79
|
let current = claims;
|
|
47
80
|
for (const seg of segments) {
|
|
48
81
|
if (current == null || typeof current !== "object")
|
|
49
|
-
return
|
|
82
|
+
return { status: "no_claim" };
|
|
50
83
|
current = current[seg];
|
|
51
84
|
}
|
|
52
|
-
return typeof current === "string" ? current :
|
|
85
|
+
return typeof current === "string" ? { status: "resolved", tenantId: current } : { status: "no_claim" };
|
|
86
|
+
}
|
|
87
|
+
function resolveFromJwt(req, claimPath = "tenant_id", options) {
|
|
88
|
+
const result = resolveJwtTenant(req, claimPath, options);
|
|
89
|
+
return result.status === "resolved" ? result.tenantId : null;
|
|
53
90
|
}
|
|
54
91
|
//# sourceMappingURL=jwt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/resolvers/jwt.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/resolvers/jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MiddlewareOptions } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Outcome of request tenant resolution. `invalid_token` means a bearer token
|
|
4
|
+
* was sent while JWT verification is configured and it did not verify; the
|
|
5
|
+
* caller must reject the request rather than try other sources.
|
|
6
|
+
*/
|
|
7
|
+
export type TenantIdResolution = {
|
|
8
|
+
status: "resolved";
|
|
9
|
+
tenantId: string;
|
|
10
|
+
} | {
|
|
11
|
+
status: "missing";
|
|
12
|
+
} | {
|
|
13
|
+
status: "invalid_token";
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the tenant ID for a request: verified JWT, then the tenant header,
|
|
17
|
+
* then custom resolvers.
|
|
18
|
+
*
|
|
19
|
+
* When `jwtSecret` or `jwtVerify` is configured, the JWT is the tenant binding:
|
|
20
|
+
* a token that fails verification is reported as `invalid_token`, and the
|
|
21
|
+
* client-supplied tenant header is not consulted unless `trustTenantHeader`
|
|
22
|
+
* is true.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveTenantId(req: unknown, options?: MiddlewareOptions): Promise<TenantIdResolution>;
|
|
25
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/resolvers/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC;AAEhC;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuB5G"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveTenantId = resolveTenantId;
|
|
4
|
+
const jwt_js_1 = require("./jwt.js");
|
|
5
|
+
const header_js_1 = require("./header.js");
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the tenant ID for a request: verified JWT, then the tenant header,
|
|
8
|
+
* then custom resolvers.
|
|
9
|
+
*
|
|
10
|
+
* When `jwtSecret` or `jwtVerify` is configured, the JWT is the tenant binding:
|
|
11
|
+
* a token that fails verification is reported as `invalid_token`, and the
|
|
12
|
+
* client-supplied tenant header is not consulted unless `trustTenantHeader`
|
|
13
|
+
* is true.
|
|
14
|
+
*/
|
|
15
|
+
async function resolveTenantId(req, options) {
|
|
16
|
+
const jwtConfigured = Boolean(options?.jwtSecret || options?.jwtVerify);
|
|
17
|
+
const jwt = (0, jwt_js_1.resolveJwtTenant)(req, options?.jwtClaimPath, {
|
|
18
|
+
secret: options?.jwtSecret,
|
|
19
|
+
verify: options?.jwtVerify,
|
|
20
|
+
});
|
|
21
|
+
if (jwt.status === "resolved")
|
|
22
|
+
return { status: "resolved", tenantId: jwt.tenantId };
|
|
23
|
+
if (jwt.status === "invalid")
|
|
24
|
+
return { status: "invalid_token" };
|
|
25
|
+
if (!jwtConfigured || options?.trustTenantHeader) {
|
|
26
|
+
const fromHeader = (0, header_js_1.resolveFromHeader)(req, options?.headerName);
|
|
27
|
+
if (fromHeader)
|
|
28
|
+
return { status: "resolved", tenantId: fromHeader };
|
|
29
|
+
}
|
|
30
|
+
if (options?.resolvers) {
|
|
31
|
+
for (const resolver of options.resolvers) {
|
|
32
|
+
const result = await resolver.resolve(req);
|
|
33
|
+
if (result)
|
|
34
|
+
return { status: "resolved", tenantId: result };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { status: "missing" };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/resolvers/resolve.ts"],"names":[],"mappings":";;AAuBA,0CAuBC;AA7CD,qCAA4C;AAC5C,2CAAgD;AAYhD;;;;;;;;GAQG;AACI,KAAK,UAAU,eAAe,CAAC,GAAY,EAAE,OAA2B;IAC7E,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,IAAI,OAAO,EAAE,SAAS,CAAC,CAAC;IAExE,MAAM,GAAG,GAAG,IAAA,yBAAgB,EAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE;QACvD,MAAM,EAAE,OAAO,EAAE,SAAS;QAC1B,MAAM,EAAE,OAAO,EAAE,SAAS;KAC3B,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;IACrF,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAEjE,IAAI,CAAC,aAAa,IAAI,OAAO,EAAE,iBAAiB,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,IAAA,6BAAiB,EAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAC/D,IAAI,UAAU;YAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IACtE,CAAC;IAED,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;QACvB,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,MAAM;gBAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,10 +3,19 @@ export type { ResolvedTenantContext, TenantNode, CreateTenantInput, UpdateTenant
|
|
|
3
3
|
export type { TenantResolver } from "./resolvers/custom.js";
|
|
4
4
|
export interface MiddlewareOptions {
|
|
5
5
|
resolvers?: import("./resolvers/custom.js").TenantResolver[];
|
|
6
|
+
/** Header to read the tenant ID from. When set, only this header is read (default: X-Tenant-ID). */
|
|
6
7
|
headerName?: string;
|
|
7
8
|
jwtClaimPath?: string;
|
|
9
|
+
/** HS256 secret for JWT verification. Requires the optional `jsonwebtoken` peer dependency unless `jwtVerify` is given. */
|
|
8
10
|
jwtSecret?: string;
|
|
9
11
|
jwtVerify?: (token: string) => Record<string, unknown> | null;
|
|
12
|
+
/**
|
|
13
|
+
* When `jwtSecret` or `jwtVerify` is configured, the tenant header is ignored
|
|
14
|
+
* unless this is true, so the verified JWT is the only tenant binding. A
|
|
15
|
+
* bearer token that fails verification is always rejected with 401. Has no
|
|
16
|
+
* effect when JWT verification is not configured. Default: false.
|
|
17
|
+
*/
|
|
18
|
+
trustTenantHeader?: boolean;
|
|
10
19
|
onError?: (err: Error, req: unknown) => void;
|
|
11
20
|
/**
|
|
12
21
|
* Enable tenant impersonation via X-Impersonate-Tenant header.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAErJ,YAAY,EAAE,qBAAqB,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC;AAE7H,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,OAAO,uBAAuB,EAAE,cAAc,EAAE,CAAC;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACxG,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;KACxF,CAAC;CACH"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAErJ,YAAY,EAAE,qBAAqB,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC;AAE7H,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,OAAO,uBAAuB,EAAE,cAAc,EAAE,CAAC;IAC7D,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2HAA2H;IAC3H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACxG,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;KACxF,CAAC;CACH"}
|