@tumbaland/backend-core 1.50.0 → 1.51.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/dist/groups/access.d.ts +13 -0
- package/dist/groups/access.d.ts.map +1 -0
- package/dist/groups/access.js +22 -0
- package/dist/groups/access.js.map +1 -0
- package/dist/groups/index.d.ts +1 -0
- package/dist/groups/index.d.ts.map +1 -1
- package/dist/groups/index.js +4 -1
- package/dist/groups/index.js.map +1 -1
- package/dist/testing/openapi.d.ts +34 -0
- package/dist/testing/openapi.d.ts.map +1 -0
- package/dist/testing/openapi.js +110 -0
- package/dist/testing/openapi.js.map +1 -0
- package/dist/testing/serviceTestSetup.js +3 -0
- package/dist/testing/serviceTestSetup.js.map +1 -1
- package/package.json +4 -2
- package/testing.d.ts +1 -0
- package/testing.js +3 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Whether `groupId` is one of the groups the person belongs to. */
|
|
2
|
+
export declare const isMemberOf: (groupId: string, userGroups: readonly string[] | null | undefined) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The one rule that keeps a group's data inside the group: a `groupId` that
|
|
5
|
+
* arrives in a request is never trusted, it must be one of the caller's own.
|
|
6
|
+
*
|
|
7
|
+
* No group named means the caller's personal data, which is always theirs, so
|
|
8
|
+
* `undefined` and `null` pass. A group they are not in throws a 403, never
|
|
9
|
+
* falls back to personal: reading someone else's group as "personal" would
|
|
10
|
+
* quietly show them nothing, and writing to it would not.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertMemberOf(groupId: string | null | undefined, userGroups: readonly string[] | null | undefined, message?: string): void;
|
|
13
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/groups/access.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,YAAY,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,KAAG,OACvD,CAAC;AAEzC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,EAChD,OAAO,SAAkB,GACxB,IAAI,CAEN"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isMemberOf = void 0;
|
|
4
|
+
exports.assertMemberOf = assertMemberOf;
|
|
5
|
+
const HttpError_1 = require("../errors/HttpError");
|
|
6
|
+
/** Whether `groupId` is one of the groups the person belongs to. */
|
|
7
|
+
const isMemberOf = (groupId, userGroups) => Boolean(userGroups?.includes(groupId));
|
|
8
|
+
exports.isMemberOf = isMemberOf;
|
|
9
|
+
/**
|
|
10
|
+
* The one rule that keeps a group's data inside the group: a `groupId` that
|
|
11
|
+
* arrives in a request is never trusted, it must be one of the caller's own.
|
|
12
|
+
*
|
|
13
|
+
* No group named means the caller's personal data, which is always theirs, so
|
|
14
|
+
* `undefined` and `null` pass. A group they are not in throws a 403, never
|
|
15
|
+
* falls back to personal: reading someone else's group as "personal" would
|
|
16
|
+
* quietly show them nothing, and writing to it would not.
|
|
17
|
+
*/
|
|
18
|
+
function assertMemberOf(groupId, userGroups, message = 'Access denied') {
|
|
19
|
+
if (groupId && !(0, exports.isMemberOf)(groupId, userGroups))
|
|
20
|
+
throw new HttpError_1.ForbiddenError(message);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/groups/access.ts"],"names":[],"mappings":";;;AAeA,wCAMC;AArBD,mDAAqD;AAErD,oEAAoE;AAC7D,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,UAAgD,EAAW,EAAE,CACvG,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAD5B,QAAA,UAAU,cACkB;AAEzC;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAC5B,OAAkC,EAClC,UAAgD,EAChD,OAAO,GAAG,eAAe;IAEzB,IAAI,OAAO,IAAI,CAAC,IAAA,kBAAU,EAAC,OAAO,EAAE,UAAU,CAAC;QAAE,MAAM,IAAI,0BAAc,CAAC,OAAO,CAAC,CAAC;AACrF,CAAC"}
|
package/dist/groups/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export type { GroupOwner } from './client';
|
|
|
3
3
|
export { groupOwnerSubject } from './subject';
|
|
4
4
|
export type { GroupIdResolver } from './subject';
|
|
5
5
|
export { getUserGroupIds, currentGroupsOf, clearMembershipCache } from './membership';
|
|
6
|
+
export { assertMemberOf, isMemberOf } from './access';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/groups/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clearMembershipCache = exports.currentGroupsOf = exports.getUserGroupIds = exports.groupOwnerSubject = exports.clearGroupOwnerCache = exports.invalidateGroupOwner = exports.getGroupOwner = void 0;
|
|
3
|
+
exports.isMemberOf = exports.assertMemberOf = exports.clearMembershipCache = exports.currentGroupsOf = exports.getUserGroupIds = exports.groupOwnerSubject = exports.clearGroupOwnerCache = exports.invalidateGroupOwner = exports.getGroupOwner = void 0;
|
|
4
4
|
var client_1 = require("./client");
|
|
5
5
|
Object.defineProperty(exports, "getGroupOwner", { enumerable: true, get: function () { return client_1.getGroupOwner; } });
|
|
6
6
|
Object.defineProperty(exports, "invalidateGroupOwner", { enumerable: true, get: function () { return client_1.invalidateGroupOwner; } });
|
|
@@ -11,4 +11,7 @@ var membership_1 = require("./membership");
|
|
|
11
11
|
Object.defineProperty(exports, "getUserGroupIds", { enumerable: true, get: function () { return membership_1.getUserGroupIds; } });
|
|
12
12
|
Object.defineProperty(exports, "currentGroupsOf", { enumerable: true, get: function () { return membership_1.currentGroupsOf; } });
|
|
13
13
|
Object.defineProperty(exports, "clearMembershipCache", { enumerable: true, get: function () { return membership_1.clearMembershipCache; } });
|
|
14
|
+
var access_1 = require("./access");
|
|
15
|
+
Object.defineProperty(exports, "assertMemberOf", { enumerable: true, get: function () { return access_1.assertMemberOf; } });
|
|
16
|
+
Object.defineProperty(exports, "isMemberOf", { enumerable: true, get: function () { return access_1.isMemberOf; } });
|
|
14
17
|
//# sourceMappingURL=index.js.map
|
package/dist/groups/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":";;;AAAA,mCAAqF;AAA5E,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAGlE,qCAA8C;AAArC,4GAAA,iBAAiB,OAAA;AAG1B,2CAAsF;AAA7E,6GAAA,eAAe,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,kHAAA,oBAAoB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/groups/index.ts"],"names":[],"mappings":";;;AAAA,mCAAqF;AAA5E,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAGlE,qCAA8C;AAArC,4GAAA,iBAAiB,OAAA;AAG1B,2CAAsF;AAA7E,6GAAA,eAAe,OAAA;AAAE,6GAAA,eAAe,OAAA;AAAE,kHAAA,oBAAoB,OAAA;AAC/D,mCAAsD;AAA7C,wGAAA,cAAc,OAAA;AAAE,oGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Application } from 'express';
|
|
2
|
+
/** Makes every `use()` remember its path on the layers it adds. Idempotent. */
|
|
3
|
+
export declare function recordMountPaths(): void;
|
|
4
|
+
export interface Endpoint {
|
|
5
|
+
method: string;
|
|
6
|
+
/** In OpenAPI form: `/api/things/{id}`. */
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every method and path the app answers, as OpenAPI would write them. */
|
|
10
|
+
export declare function listRoutes(app: Application): Endpoint[];
|
|
11
|
+
/**
|
|
12
|
+
* Every method and path a spec documents. A path item may live in a file of its
|
|
13
|
+
* own (`$ref: 'paths/thing.yml'`), as file-service's source spec does, so a
|
|
14
|
+
* relative `$ref` is followed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function listDocumented(specFile: string): Endpoint[];
|
|
17
|
+
/**
|
|
18
|
+
* Served by backend-core in every service, for the platform rather than for API
|
|
19
|
+
* callers: Kubernetes-style liveness and Prometheus. Not expected in each spec.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PLATFORM_ROUTES: string[];
|
|
22
|
+
export interface OpenApiDrift {
|
|
23
|
+
/** Routes the app serves that the spec does not mention. */
|
|
24
|
+
undocumented: string[];
|
|
25
|
+
/** Operations the spec describes that the app does not serve. */
|
|
26
|
+
stale: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What the spec and the app disagree on. `ignore` takes routes that are
|
|
30
|
+
* deliberately left out of the docs (`'GET /docs'`, or a pattern), on top of
|
|
31
|
+
* the platform routes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function openApiDrift(app: Application, specFile: string, ignore?: Array<string | RegExp>): OpenApiDrift;
|
|
34
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/testing/openapi.ts"],"names":[],"mappings":"AAEA,OAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAyBpD,+EAA+E;AAC/E,wBAAgB,gBAAgB,IAAI,IAAI,CAavC;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,QAAQ,EAAE,CAsBvD;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAY3D;AAMD;;;GAGG;AACH,eAAO,MAAM,eAAe,UAAuC,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iEAAiE;IACjE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAM,GAAG,YAAY,CAalH"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PLATFORM_ROUTES = void 0;
|
|
7
|
+
exports.recordMountPaths = recordMountPaths;
|
|
8
|
+
exports.listRoutes = listRoutes;
|
|
9
|
+
exports.listDocumented = listDocumented;
|
|
10
|
+
exports.openApiDrift = openApiDrift;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const express_1 = __importDefault(require("express"));
|
|
14
|
+
const yaml_1 = require("yaml");
|
|
15
|
+
let recording = false;
|
|
16
|
+
/** Makes every `use()` remember its path on the layers it adds. Idempotent. */
|
|
17
|
+
function recordMountPaths() {
|
|
18
|
+
if (recording)
|
|
19
|
+
return;
|
|
20
|
+
recording = true;
|
|
21
|
+
const proto = express_1.default.Router.prototype;
|
|
22
|
+
const use = proto.use;
|
|
23
|
+
proto.use = function (...args) {
|
|
24
|
+
const before = this.stack.length;
|
|
25
|
+
const result = use.apply(this, args);
|
|
26
|
+
const first = args[0];
|
|
27
|
+
const mountPath = typeof first === 'string' || Array.isArray(first) ? first : '/';
|
|
28
|
+
for (const layer of this.stack.slice(before))
|
|
29
|
+
layer.mountPath = mountPath;
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
34
|
+
const join = (prefix, path) => {
|
|
35
|
+
const joined = `${prefix}/${path}`.replace(/\/+/g, '/');
|
|
36
|
+
return joined.length > 1 ? joined.replace(/\/$/, '') : joined;
|
|
37
|
+
};
|
|
38
|
+
/** `/:id` and Express 5's `/*fileId` as OpenAPI writes them: `/{id}`, `/{fileId}`. */
|
|
39
|
+
const toOpenApiPath = (path) => path.replace(/[:*](\w+)/g, '{$1}');
|
|
40
|
+
/** Every method and path the app answers, as OpenAPI would write them. */
|
|
41
|
+
function listRoutes(app) {
|
|
42
|
+
const router = app.router ?? app._router;
|
|
43
|
+
if (!router)
|
|
44
|
+
throw new Error('The app has no router yet: build it before listing its routes');
|
|
45
|
+
const found = [];
|
|
46
|
+
const walk = (stack, prefix) => {
|
|
47
|
+
for (const layer of stack) {
|
|
48
|
+
if (layer.route) {
|
|
49
|
+
const paths = Array.isArray(layer.route.path) ? layer.route.path : [layer.route.path];
|
|
50
|
+
for (const path of paths) {
|
|
51
|
+
for (const method of Object.keys(layer.route.methods).filter((m) => METHODS.includes(m))) {
|
|
52
|
+
found.push({ method, path: toOpenApiPath(join(prefix, path)) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (layer.handle?.stack) {
|
|
57
|
+
const mounts = layer.mountPath === undefined ? ['/'] : [].concat(layer.mountPath);
|
|
58
|
+
for (const mount of mounts)
|
|
59
|
+
walk(layer.handle.stack, join(prefix, mount));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(router.stack, '/');
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
const readYaml = (file) => (0, yaml_1.parse)(fs_1.default.readFileSync(file, 'utf8'));
|
|
67
|
+
/**
|
|
68
|
+
* Every method and path a spec documents. A path item may live in a file of its
|
|
69
|
+
* own (`$ref: 'paths/thing.yml'`), as file-service's source spec does, so a
|
|
70
|
+
* relative `$ref` is followed.
|
|
71
|
+
*/
|
|
72
|
+
function listDocumented(specFile) {
|
|
73
|
+
const spec = readYaml(specFile);
|
|
74
|
+
return Object.entries(spec.paths ?? {}).flatMap(([path, item]) => {
|
|
75
|
+
const ref = item?.$ref;
|
|
76
|
+
const operations = typeof ref === 'string' && !ref.startsWith('#')
|
|
77
|
+
? readYaml(path_1.default.resolve(path_1.default.dirname(specFile), ref))
|
|
78
|
+
: item;
|
|
79
|
+
return Object.keys(operations ?? {})
|
|
80
|
+
.filter((method) => METHODS.includes(method))
|
|
81
|
+
.map((method) => ({ method, path }));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** A route's identity regardless of what its parameters are called: `{id}` and `{conversationId}` match. */
|
|
85
|
+
const key = ({ method, path }) => `${method.toUpperCase()} ${path.replace(/\{[^}]+\}/g, '{}')}`;
|
|
86
|
+
const label = ({ method, path }) => `${method.toUpperCase()} ${path}`;
|
|
87
|
+
/**
|
|
88
|
+
* Served by backend-core in every service, for the platform rather than for API
|
|
89
|
+
* callers: Kubernetes-style liveness and Prometheus. Not expected in each spec.
|
|
90
|
+
*/
|
|
91
|
+
exports.PLATFORM_ROUTES = ['GET /health/live', 'GET /metrics'];
|
|
92
|
+
/**
|
|
93
|
+
* What the spec and the app disagree on. `ignore` takes routes that are
|
|
94
|
+
* deliberately left out of the docs (`'GET /docs'`, or a pattern), on top of
|
|
95
|
+
* the platform routes.
|
|
96
|
+
*/
|
|
97
|
+
function openApiDrift(app, specFile, ignore = []) {
|
|
98
|
+
const rules = [...exports.PLATFORM_ROUTES, ...ignore];
|
|
99
|
+
const skipped = (endpoint) => rules.some((rule) => (typeof rule === 'string' ? rule === label(endpoint) : rule.test(label(endpoint))));
|
|
100
|
+
const routes = listRoutes(app).filter((endpoint) => !skipped(endpoint));
|
|
101
|
+
const documented = listDocumented(specFile).filter((endpoint) => !skipped(endpoint));
|
|
102
|
+
const routeKeys = new Set(routes.map(key));
|
|
103
|
+
const documentedKeys = new Set(documented.map(key));
|
|
104
|
+
const unique = (labels) => [...new Set(labels)].sort();
|
|
105
|
+
return {
|
|
106
|
+
undocumented: unique(routes.filter((endpoint) => !documentedKeys.has(key(endpoint))).map(label)),
|
|
107
|
+
stale: unique(documented.filter((endpoint) => !routeKeys.has(key(endpoint))).map(label))
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/testing/openapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const session_1 = require("../auth/session");
|
|
4
4
|
const membership_1 = require("../groups/membership");
|
|
5
|
+
const openapi_1 = require("./openapi");
|
|
5
6
|
/**
|
|
6
7
|
* Jest setup for service test suites: they sign session tokens without a
|
|
7
8
|
* session and run with no database or group-service, so every token's session
|
|
@@ -15,4 +16,6 @@ const membership_1 = require("../groups/membership");
|
|
|
15
16
|
// No group-service either: a test token's own `groups` claim is taken as the
|
|
16
17
|
// person's membership. The live lookup is tested in groups/membership.test.ts.
|
|
17
18
|
(0, membership_1.overrideMembershipLookupForTests)(async (user) => user.groups ?? []);
|
|
19
|
+
// Before any app is built, so the OpenAPI drift check can see where routers are mounted.
|
|
20
|
+
(0, openapi_1.recordMountPaths)();
|
|
18
21
|
//# sourceMappingURL=serviceTestSetup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceTestSetup.js","sourceRoot":"","sources":["../../src/testing/serviceTestSetup.ts"],"names":[],"mappings":";;AAAA,6CAA+D;AAC/D,qDAAwE;
|
|
1
|
+
{"version":3,"file":"serviceTestSetup.js","sourceRoot":"","sources":["../../src/testing/serviceTestSetup.ts"],"names":[],"mappings":";;AAAA,6CAA+D;AAC/D,qDAAwE;AACxE,uCAA6C;AAE7C;;;;;;;;GAQG;AACH,IAAA,sCAA4B,EAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAE/C,6EAA6E;AAC7E,+EAA+E;AAC/E,IAAA,6CAAgC,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AAEpE,yFAAyF;AACzF,IAAA,0BAAgB,GAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tumbaland/backend-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
4
4
|
"description": "Core shared functionality for Tumbaland backend services",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
|
-
"testing.js"
|
|
9
|
+
"testing.js",
|
|
10
|
+
"testing.d.ts"
|
|
10
11
|
],
|
|
11
12
|
"scripts": {
|
|
12
13
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"prom-client": "^15.1.3",
|
|
61
62
|
"uuid": "^14.0.3",
|
|
62
63
|
"winston": "^3.19.0",
|
|
64
|
+
"yaml": "^2.9.1",
|
|
63
65
|
"zod": "^4.6.7"
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
package/testing.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dist/testing/openapi';
|
package/testing.js
CHANGED