@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 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
@@ -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 };
@@ -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
+ }