@johnmmackey/app-toolkit 0.1.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 +141 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +95 -0
- package/dist/index.mjs +57 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# app-toolkit
|
|
2
|
+
|
|
3
|
+
A collection of shared utility functions for common application tasks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @johnmmackey/app-toolkit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { } from '@johnmmackey/app-toolkit';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Development
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- Node.js >= 18
|
|
22
|
+
- npm >= 9
|
|
23
|
+
|
|
24
|
+
### Setup
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Scripts
|
|
31
|
+
|
|
32
|
+
| Command | Description |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `npm run build` | Compile TypeScript to `dist/` |
|
|
35
|
+
| `npm run dev` | Build in watch mode |
|
|
36
|
+
| `npm test` | Run tests |
|
|
37
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
38
|
+
| `npm run typecheck` | Type-check without emitting |
|
|
39
|
+
| `npm run lint` | Lint source files |
|
|
40
|
+
|
|
41
|
+
### Project Structure
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
app-toolkit/
|
|
45
|
+
├── src/
|
|
46
|
+
│ └── index.ts # Public API entry point
|
|
47
|
+
├── dist/ # Compiled output (generated)
|
|
48
|
+
├── package.json
|
|
49
|
+
├── tsconfig.json
|
|
50
|
+
└── README.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `getLogger(label: string): Logger`
|
|
56
|
+
|
|
57
|
+
Returns a [Winston](https://github.com/winstonjs/winston) child logger bound to the given module label.
|
|
58
|
+
|
|
59
|
+
Every log entry is emitted as structured JSON with a `timestamp` and `label` field, ready to be ingested by any log aggregator.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { getLogger } from '@johnmmackey/app-toolkit';
|
|
63
|
+
|
|
64
|
+
const log = getLogger('my-module');
|
|
65
|
+
|
|
66
|
+
log.info('server started', { port: 3000 });
|
|
67
|
+
// → {"level":"info","label":"my-module","port":3000,"timestamp":"..."}
|
|
68
|
+
|
|
69
|
+
log.error('unexpected failure', new Error('oops'));
|
|
70
|
+
// → {"level":"error","label":"my-module","message":"oops","stack":"...","timestamp":"..."}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Environment variables**
|
|
74
|
+
|
|
75
|
+
| Variable | Default | Description |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `LOG_LEVEL` | `"info"` | Global log level (`error` \| `warn` \| `info` \| `debug`) |
|
|
78
|
+
| `APP_DEBUG_MODULES` | — | Space-separated list of [micromatch](https://github.com/micromatch/micromatch) glob patterns. Any logger whose `label` matches is promoted to `debug` level regardless of `LOG_LEVEL`. |
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# promote only the "db" and "auth-*" loggers to debug
|
|
82
|
+
APP_DEBUG_MODULES="db auth-*" node dist/server.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### `getAssumedRoleCredentials(roleArn: string, region?: string): Promise<AwsCredentialIdentity>`
|
|
88
|
+
|
|
89
|
+
Assumes an AWS IAM role via STS and returns temporary credentials. Results are cached in-process and automatically refreshed 5 minutes before expiry.
|
|
90
|
+
|
|
91
|
+
| Parameter | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `roleArn` | `string` | — | ARN of the role to assume (required) |
|
|
94
|
+
| `region` | `string` | `"us-east-1"` | AWS region for the STS API call |
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { getAssumedRoleCredentials } from '@johnmmackey/app-toolkit';
|
|
98
|
+
|
|
99
|
+
const creds = await getAssumedRoleCredentials(
|
|
100
|
+
'arn:aws:iam::123456789012:role/MyRole',
|
|
101
|
+
'eu-west-1',
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Pass credentials to any AWS SDK client
|
|
105
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
106
|
+
const s3 = new S3Client({ credentials: creds, region: 'eu-west-1' });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Credential resolution (STS client)**
|
|
110
|
+
- **Dev**: relies on environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) or a shared credentials file.
|
|
111
|
+
- **Prod / ECS / Lambda**: relies on the instance / task metadata service (IMDSv2).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Types
|
|
116
|
+
|
|
117
|
+
Named types used by this package are re-exported from the `/types` sub-path and also via the `AppToolkitTypes` namespace for convenience.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// Individual type imports
|
|
121
|
+
import type { AppLogger, AwsCredentialIdentity } from '@johnmmackey/app-toolkit';
|
|
122
|
+
|
|
123
|
+
// Namespace import
|
|
124
|
+
import type { AppToolkitTypes } from '@johnmmackey/app-toolkit';
|
|
125
|
+
type MyLogger = AppToolkitTypes.AppLogger;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Contributing
|
|
129
|
+
|
|
130
|
+
1. Create a feature branch from `main`
|
|
131
|
+
2. Make your changes with tests
|
|
132
|
+
3. Run `npm test` and `npm run typecheck` to verify
|
|
133
|
+
4. Open a pull request
|
|
134
|
+
|
|
135
|
+
## Changelog
|
|
136
|
+
|
|
137
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
import winston__default from 'winston';
|
|
3
|
+
import * as _aws_sdk_types from '@aws-sdk/types';
|
|
4
|
+
import { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Application-wide Winston logger.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { getLogger } from "@/lib/logger";
|
|
11
|
+
* const log = getLogger("my-module");
|
|
12
|
+
* log.info("something happened", { extra: "metadata" });
|
|
13
|
+
*
|
|
14
|
+
* Log level is controlled by the LOG_LEVEL environment variable (default: "info").
|
|
15
|
+
* Valid values: error | warn | info | debug
|
|
16
|
+
*
|
|
17
|
+
* All output is JSON with a timestamp, making it trivial to ingest into any
|
|
18
|
+
* log aggregator. The `label` field identifies the originating module.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns a child logger bound to the given label.
|
|
23
|
+
* The label is included in every log entry as `{ label: "..." }`.
|
|
24
|
+
*/
|
|
25
|
+
declare function getLogger(label: string): winston__default.Logger;
|
|
26
|
+
|
|
27
|
+
declare function getAssumedRoleCredentials(roleArn: string, region?: string): Promise<AwsCredentialIdentity>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convenience namespace — import everything from here to avoid multiple
|
|
31
|
+
* type-only imports:
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* import type { AppToolkitTypes } from '@common-modules/app-toolkit';
|
|
35
|
+
* type MyLogger = AppToolkitTypes.AppLogger;
|
|
36
|
+
*/
|
|
37
|
+
declare namespace AppToolkitTypes {
|
|
38
|
+
type AppLogger = winston.Logger;
|
|
39
|
+
type AwsCredentialIdentity = _aws_sdk_types.AwsCredentialIdentity;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { AppToolkitTypes, getAssumedRoleCredentials, getLogger };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
import winston__default from 'winston';
|
|
3
|
+
import * as _aws_sdk_types from '@aws-sdk/types';
|
|
4
|
+
import { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Application-wide Winston logger.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { getLogger } from "@/lib/logger";
|
|
11
|
+
* const log = getLogger("my-module");
|
|
12
|
+
* log.info("something happened", { extra: "metadata" });
|
|
13
|
+
*
|
|
14
|
+
* Log level is controlled by the LOG_LEVEL environment variable (default: "info").
|
|
15
|
+
* Valid values: error | warn | info | debug
|
|
16
|
+
*
|
|
17
|
+
* All output is JSON with a timestamp, making it trivial to ingest into any
|
|
18
|
+
* log aggregator. The `label` field identifies the originating module.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns a child logger bound to the given label.
|
|
23
|
+
* The label is included in every log entry as `{ label: "..." }`.
|
|
24
|
+
*/
|
|
25
|
+
declare function getLogger(label: string): winston__default.Logger;
|
|
26
|
+
|
|
27
|
+
declare function getAssumedRoleCredentials(roleArn: string, region?: string): Promise<AwsCredentialIdentity>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convenience namespace — import everything from here to avoid multiple
|
|
31
|
+
* type-only imports:
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* import type { AppToolkitTypes } from '@common-modules/app-toolkit';
|
|
35
|
+
* type MyLogger = AppToolkitTypes.AppLogger;
|
|
36
|
+
*/
|
|
37
|
+
declare namespace AppToolkitTypes {
|
|
38
|
+
type AppLogger = winston.Logger;
|
|
39
|
+
type AwsCredentialIdentity = _aws_sdk_types.AwsCredentialIdentity;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { AppToolkitTypes, getAssumedRoleCredentials, getLogger };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
getAssumedRoleCredentials: () => getAssumedRoleCredentials,
|
|
34
|
+
getLogger: () => getLogger
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/logger.ts
|
|
39
|
+
var import_winston = __toESM(require("winston"));
|
|
40
|
+
var import_micromatch = __toESM(require("micromatch"));
|
|
41
|
+
var { combine, timestamp, json, errors } = import_winston.default.format;
|
|
42
|
+
var baseLogger = import_winston.default.createLogger({
|
|
43
|
+
level: process.env.LOG_LEVEL ?? "info",
|
|
44
|
+
format: combine(
|
|
45
|
+
errors({ stack: true }),
|
|
46
|
+
timestamp(),
|
|
47
|
+
json()
|
|
48
|
+
),
|
|
49
|
+
transports: [new import_winston.default.transports.Console()]
|
|
50
|
+
});
|
|
51
|
+
function getLogger(label) {
|
|
52
|
+
return baseLogger.child({
|
|
53
|
+
label,
|
|
54
|
+
level: process.env.APP_DEBUG_MODULES && import_micromatch.default.isMatch(label, process.env.APP_DEBUG_MODULES.split(" ")) ? "debug" : baseLogger.level
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/aws-role-credentials.ts
|
|
59
|
+
var import_client_sts = require("@aws-sdk/client-sts");
|
|
60
|
+
var DEFAULT_REGION = "us-east-1";
|
|
61
|
+
var cachedCredentials = null;
|
|
62
|
+
var credentialsExpiration = null;
|
|
63
|
+
async function getAssumedRoleCredentials(roleArn, region = DEFAULT_REGION) {
|
|
64
|
+
if (cachedCredentials && credentialsExpiration && /* @__PURE__ */ new Date() < credentialsExpiration) {
|
|
65
|
+
return cachedCredentials;
|
|
66
|
+
}
|
|
67
|
+
if (!roleArn) {
|
|
68
|
+
throw new Error("roleArn is required to assume role");
|
|
69
|
+
}
|
|
70
|
+
const stsClient = new import_client_sts.STSClient({
|
|
71
|
+
region
|
|
72
|
+
});
|
|
73
|
+
const command = new import_client_sts.AssumeRoleCommand({
|
|
74
|
+
RoleArn: roleArn,
|
|
75
|
+
RoleSessionName: `app-${Date.now()}`,
|
|
76
|
+
DurationSeconds: 3600
|
|
77
|
+
// 1 hour
|
|
78
|
+
});
|
|
79
|
+
const response = await stsClient.send(command);
|
|
80
|
+
if (!response.Credentials) {
|
|
81
|
+
throw new Error("Failed to assume role: no credentials returned");
|
|
82
|
+
}
|
|
83
|
+
cachedCredentials = {
|
|
84
|
+
accessKeyId: response.Credentials.AccessKeyId,
|
|
85
|
+
secretAccessKey: response.Credentials.SecretAccessKey,
|
|
86
|
+
sessionToken: response.Credentials.SessionToken
|
|
87
|
+
};
|
|
88
|
+
credentialsExpiration = new Date(response.Credentials.Expiration.getTime() - 5 * 60 * 1e3);
|
|
89
|
+
return cachedCredentials;
|
|
90
|
+
}
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
getAssumedRoleCredentials,
|
|
94
|
+
getLogger
|
|
95
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import winston from "winston";
|
|
3
|
+
import micromatch from "micromatch";
|
|
4
|
+
var { combine, timestamp, json, errors } = winston.format;
|
|
5
|
+
var baseLogger = winston.createLogger({
|
|
6
|
+
level: process.env.LOG_LEVEL ?? "info",
|
|
7
|
+
format: combine(
|
|
8
|
+
errors({ stack: true }),
|
|
9
|
+
timestamp(),
|
|
10
|
+
json()
|
|
11
|
+
),
|
|
12
|
+
transports: [new winston.transports.Console()]
|
|
13
|
+
});
|
|
14
|
+
function getLogger(label) {
|
|
15
|
+
return baseLogger.child({
|
|
16
|
+
label,
|
|
17
|
+
level: process.env.APP_DEBUG_MODULES && micromatch.isMatch(label, process.env.APP_DEBUG_MODULES.split(" ")) ? "debug" : baseLogger.level
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/aws-role-credentials.ts
|
|
22
|
+
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
|
|
23
|
+
var DEFAULT_REGION = "us-east-1";
|
|
24
|
+
var cachedCredentials = null;
|
|
25
|
+
var credentialsExpiration = null;
|
|
26
|
+
async function getAssumedRoleCredentials(roleArn, region = DEFAULT_REGION) {
|
|
27
|
+
if (cachedCredentials && credentialsExpiration && /* @__PURE__ */ new Date() < credentialsExpiration) {
|
|
28
|
+
return cachedCredentials;
|
|
29
|
+
}
|
|
30
|
+
if (!roleArn) {
|
|
31
|
+
throw new Error("roleArn is required to assume role");
|
|
32
|
+
}
|
|
33
|
+
const stsClient = new STSClient({
|
|
34
|
+
region
|
|
35
|
+
});
|
|
36
|
+
const command = new AssumeRoleCommand({
|
|
37
|
+
RoleArn: roleArn,
|
|
38
|
+
RoleSessionName: `app-${Date.now()}`,
|
|
39
|
+
DurationSeconds: 3600
|
|
40
|
+
// 1 hour
|
|
41
|
+
});
|
|
42
|
+
const response = await stsClient.send(command);
|
|
43
|
+
if (!response.Credentials) {
|
|
44
|
+
throw new Error("Failed to assume role: no credentials returned");
|
|
45
|
+
}
|
|
46
|
+
cachedCredentials = {
|
|
47
|
+
accessKeyId: response.Credentials.AccessKeyId,
|
|
48
|
+
secretAccessKey: response.Credentials.SecretAccessKey,
|
|
49
|
+
sessionToken: response.Credentials.SessionToken
|
|
50
|
+
};
|
|
51
|
+
credentialsExpiration = new Date(response.Credentials.Expiration.getTime() - 5 * 60 * 1e3);
|
|
52
|
+
return cachedCredentials;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
getAssumedRoleCredentials,
|
|
56
|
+
getLogger
|
|
57
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@johnmmackey/app-toolkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A collection of shared utility functions",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"lint": "eslint src --ext .ts",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"utilities",
|
|
29
|
+
"toolkit",
|
|
30
|
+
"helpers"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/micromatch": "^4.0.10",
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"vitest": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@aws-sdk/client-sts": "^3.1009.0",
|
|
43
|
+
"@aws-sdk/types": "^3.973.6",
|
|
44
|
+
"micromatch": "^4.0.8",
|
|
45
|
+
"winston": "^3.19.0"
|
|
46
|
+
}
|
|
47
|
+
}
|