gcf-common-lib 0.35.0 → 0.38.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/.github/workflows/npm-publish.yml +39 -36
- package/.junie/guidelines.md +108 -0
- package/.prettierrc +10 -10
- package/CODE_OF_CONDUCT.md +13 -0
- package/CONTRIBUTING.md +60 -0
- package/LICENSE +21 -0
- package/README.md +110 -1
- package/docs/plan.md +211 -0
- package/docs/tasks.md +88 -0
- package/eslint.config.mjs +31 -31
- package/package.json +61 -52
- package/src/{amqp-helper.ts → amqp-helper.ts.old} +52 -52
- package/src/index.js +191 -188
- package/src/index.ts +177 -178
- package/src/mongo-helper.js +42 -42
- package/src/mongo-helper.ts +51 -51
- package/src/mongo-lock.js +86 -86
- package/src/mongo-lock.ts +99 -99
- package/src/types.js +2 -2
- package/src/types.ts +84 -84
- package/src/utils.js +105 -84
- package/src/utils.ts +110 -88
- package/test/gcf-common.process.test.js +63 -0
- package/test/gcf-common.process.test.ts +65 -0
- package/test/metadata.test.js +77 -0
- package/test/metadata.test.ts +78 -0
- package/test/sample.test.js +12 -0
- package/test/test.js +30 -25
- package/test/test.ts +32 -26
- package/test/ts-tests.test.js +8 -0
- package/test/utils.test.js +43 -0
- package/test/utils.test.ts +47 -0
- package/tsconfig.json +7 -7
- package/src/amqp-helper.js +0 -44
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
on: [ push ]
|
|
6
|
-
|
|
7
|
-
# on:
|
|
8
|
-
# release:
|
|
9
|
-
# types: [created]
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
- uses: actions/setup-node@v4
|
|
17
|
-
with:
|
|
18
|
-
node-version: 20
|
|
19
|
-
- run: npm ci
|
|
20
|
-
# - run: npm run build
|
|
21
|
-
- run: npm test
|
|
22
|
-
|
|
23
|
-
publish-npm:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Node.js Package
|
|
5
|
+
on: [ push ]
|
|
6
|
+
|
|
7
|
+
# on:
|
|
8
|
+
# release:
|
|
9
|
+
# types: [created]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 20
|
|
19
|
+
- run: npm ci
|
|
20
|
+
# - run: npm run build
|
|
21
|
+
- run: npm test
|
|
22
|
+
|
|
23
|
+
publish-npm:
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read # This is required for actions/checkout
|
|
26
|
+
id-token: write # This is required for publishing to npm
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: actions/setup-node@v4
|
|
32
|
+
with:
|
|
33
|
+
node-version: 20
|
|
34
|
+
registry-url: https://registry.npmjs.org/
|
|
35
|
+
- run: npm ci
|
|
36
|
+
# - run: npm run build
|
|
37
|
+
- run: npm publish --provenance
|
|
38
|
+
# env:
|
|
39
|
+
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# gcf-common-lib: Development Guidelines
|
|
2
|
+
|
|
3
|
+
This document captures project-specific details to speed up work on this repository.
|
|
4
|
+
It assumes an advanced Node/TypeScript developer familiar with Node 20+, npm, ESLint flat config, and the Node test runner.
|
|
5
|
+
|
|
6
|
+
## 1) Build and Configuration
|
|
7
|
+
|
|
8
|
+
- Runtime/Tooling versions
|
|
9
|
+
- Node: >= 20 (see package.json "engines"). Tested with Node 20.x.
|
|
10
|
+
- TypeScript: ^5.3 (dev dependency). The project extends `@tsconfig/node20`.
|
|
11
|
+
- TypeScript config
|
|
12
|
+
- tsconfig.json extends `@tsconfig/node20`. It currently sets only `"compilerOptions.newLine": "crlf"`.
|
|
13
|
+
- `@tsconfig/node20` typically configures strict mode and `noEmit: true`. As a result, `npm run build` performs type-checking only and does not emit JS artifacts by default.
|
|
14
|
+
- If you need JS output, add an `outDir` and override `noEmit: false` in tsconfig.json (or create a separate tsconfig.build.json) and adjust the build script accordingly.
|
|
15
|
+
- Build
|
|
16
|
+
- Type-check: `npm run build` (runs `tsc`). No compiled JS will be emitted with the current config.
|
|
17
|
+
- Entry point / module resolution
|
|
18
|
+
- package.json sets `"main": "src/index"`. The published package is intended to be consumed by TS-aware builds, bundlers, or via transpilation. Node alone cannot `require('src')` TypeScript without a loader/transpiler.
|
|
19
|
+
- Linting/Formatting
|
|
20
|
+
- ESLint flat config with:
|
|
21
|
+
- `@eslint/js` (base JS rules), `typescript-eslint` (TS rules), `eslint-plugin-unicorn`, and `eslint-plugin-promise`.
|
|
22
|
+
- Notable overrides: several `@typescript-eslint/*` rules are off (no-explicit-any, no-unused-vars, no-empty-function, no-empty-interface). `unicorn/prevent-abbreviations` is off. Additional rules enforce `block-scoped-var`, `no-loop-func`.
|
|
23
|
+
- Prettier is present as a dev dependency; there is no explicit npm script, run via `npx prettier --write .` if needed.
|
|
24
|
+
- Line endings: CRLF (Windows) per tsconfig; ensure your editor respects this to avoid noisy diffs.
|
|
25
|
+
|
|
26
|
+
## 2) Testing
|
|
27
|
+
|
|
28
|
+
- Framework
|
|
29
|
+
- Uses Node’s built-in test runner (`node:test`) with `node:assert/strict`.
|
|
30
|
+
- Default npm script: `npm test` runs `node --test ./test/`.
|
|
31
|
+
|
|
32
|
+
- Important notes for this repo
|
|
33
|
+
- Some code paths interact with external services (Google Pub/Sub via `@google-cloud/pubsub`, AMQP via `amqplib`). Avoid hitting those in unit tests unless you have proper credentials and brokers.
|
|
34
|
+
- The repository contains `test/test.ts` (TypeScript) and a historical compiled `test/test.js`. The JS test tries to `require('../src')`, but the project does not emit JS builds by default, so Node cannot load TS sources as JS. Running the whole suite may therefore fail locally unless you:
|
|
35
|
+
- Compile TS to JS and point Node to the emitted entry, or
|
|
36
|
+
- Use a loader (e.g., ts-node / swc / tsx) to run TS directly, or
|
|
37
|
+
- Write tests in JS that import only pure functions that do not require transpilation.
|
|
38
|
+
|
|
39
|
+
- Recommended local workflows
|
|
40
|
+
1) Run a single, deterministic JS test file (no external deps):
|
|
41
|
+
- Place a JS test under `test/` with a `.test.js` name, then run:
|
|
42
|
+
- `node --test .\test\your.test.js`
|
|
43
|
+
- Example (verified locally):
|
|
44
|
+
```js
|
|
45
|
+
// test/sample.test.js
|
|
46
|
+
const { describe, it } = require('node:test');
|
|
47
|
+
const assert = require('node:assert/strict');
|
|
48
|
+
describe('sample', () => {
|
|
49
|
+
it('adds numbers', () => {
|
|
50
|
+
assert.equal(1 + 1, 2);
|
|
51
|
+
});
|
|
52
|
+
it('async works', async () => {
|
|
53
|
+
const v = await Promise.resolve('ok');
|
|
54
|
+
assert.equal(v, 'ok');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
- Run: `node --test .\test\sample.test.js`
|
|
59
|
+
|
|
60
|
+
2) Testing library functions without network calls:
|
|
61
|
+
- Prefer testing pure utilities from `src/utils.ts` (e.g., `ms`, `sec`, `A1` conversions, `safeJsonParse`). Create JS shims if needed or compile.
|
|
62
|
+
- For `GcfCommon.process(...)`, avoid network I/O by passing payloads that do not contain `topic`, `exchange`, or `queue` in metadata/attributes. The `response(...)` method publishes only if those are present. Example payload for a no-network test:
|
|
63
|
+
```ts
|
|
64
|
+
const payload = { event: { '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', json: { ok: 1 }, attributes: {} }, context: undefined };
|
|
65
|
+
// No topic/exchange/queue -> response() becomes a no-op for network I/O
|
|
66
|
+
```
|
|
67
|
+
- Alternatively, stub `GcfCommon.response` in tests:
|
|
68
|
+
```js
|
|
69
|
+
const { GcfCommon } = require('../src');
|
|
70
|
+
const original = GcfCommon.response;
|
|
71
|
+
GcfCommon.response = async () => {}; // no-op
|
|
72
|
+
// ... run test logic ...
|
|
73
|
+
GcfCommon.response = original;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
3) Running TS tests
|
|
77
|
+
- Option A (compile): configure tsconfig to emit JS (e.g., to `dist/`), compile tests and source, and run Node against the emitted JS.
|
|
78
|
+
- Option B (loader): use a TS loader like `tsx` or `ts-node` to run TS under Node’s test runner. Example with tsx:
|
|
79
|
+
- Install dev dep: `npm i -D tsx`
|
|
80
|
+
- Run: `node --test --import tsx .\test\test.ts`
|
|
81
|
+
- Caveat: loader flags and support differ across Node versions; prefer pure JS tests if you want zero-config.
|
|
82
|
+
|
|
83
|
+
- Integration tests (Pub/Sub, AMQP)
|
|
84
|
+
- Pub/Sub requires Google Application Default Credentials (e.g., set `GOOGLE_APPLICATION_CREDENTIALS=path\to\service-account.json`).
|
|
85
|
+
- AMQP requires a reachable broker URL; set `GcfCommon.amqpOptions.url` appropriately.
|
|
86
|
+
- These are not required for unit testing and should be skipped or stubbed in CI unless explicitly needed.
|
|
87
|
+
|
|
88
|
+
## 3) Additional development information
|
|
89
|
+
|
|
90
|
+
- Project layout
|
|
91
|
+
- `src/`: TypeScript sources (helpers for Pub/Sub, AMQP, Mongo, utilities, types, and `GcfCommon`).
|
|
92
|
+
- `test/`: Node test runner tests. Prefer `.test.js` files for frictionless execution without transpilation.
|
|
93
|
+
- External services
|
|
94
|
+
- `GcfCommon.response(...)` conditionally publishes to Pub/Sub or AMQP if `topic`, `exchange`, or `queue` are present in payload metadata/attributes. Omit these in unit tests to avoid network calls.
|
|
95
|
+
- `GcfCommon.getOptions(...)` parses JSON options from metadata/attributes; it uses `safeJsonParse` with a fallback to avoid throwing.
|
|
96
|
+
- Coding style
|
|
97
|
+
- Adhere to the ESLint setup, especially around promises and unicorn rules; long-lived abbreviations are allowed (prevent-abbreviations is off).
|
|
98
|
+
- Use CRLF line endings to match repo configuration.
|
|
99
|
+
- CI/CD & publishing
|
|
100
|
+
- `publishConfig.access: public`; branch is `master` in metadata. If publishing, ensure you build/type-check and validate tests that do not require external services, or segregate integration tests under a separate command.
|
|
101
|
+
|
|
102
|
+
## Quick Commands
|
|
103
|
+
|
|
104
|
+
- Install deps: `npm ci`
|
|
105
|
+
- Type-check build: `npm run build`
|
|
106
|
+
- Run all JS tests in a file: `node --test .\test\some.test.js`
|
|
107
|
+
- Lint (manual): `npx eslint .`
|
|
108
|
+
- Format (manual): `npx prettier --write .`
|
package/.prettierrc
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
{
|
|
2
|
-
"printWidth": 120,
|
|
3
|
-
"trailingComma": "all",
|
|
4
|
-
"tabWidth": 2,
|
|
5
|
-
"semi": true,
|
|
6
|
-
"singleQuote": true,
|
|
7
|
-
"arrowParens": "avoid",
|
|
8
|
-
"bracketSameLine": true,
|
|
9
|
-
"endOfLine": "crlf"
|
|
10
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 120,
|
|
3
|
+
"trailingComma": "all",
|
|
4
|
+
"tabWidth": 2,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"singleQuote": true,
|
|
7
|
+
"arrowParens": "avoid",
|
|
8
|
+
"bracketSameLine": true,
|
|
9
|
+
"endOfLine": "crlf"
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
We follow the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
|
|
4
|
+
|
|
5
|
+
- Be respectful and inclusive.
|
|
6
|
+
- Use welcoming and inclusive language.
|
|
7
|
+
- Be considerate of differing viewpoints and experiences.
|
|
8
|
+
- Gracefully accept constructive criticism.
|
|
9
|
+
- Focus on what is best for the community.
|
|
10
|
+
|
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting the maintainers.
|
|
12
|
+
|
|
13
|
+
For more details, see https://www.contributor-covenant.org/version/2/1/code_of_conduct/
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contributing to gcf-common-lib
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing!
|
|
4
|
+
|
|
5
|
+
This project targets Node >= 20 and TypeScript ^5.3. It uses ESLint (flat config) and Node’s built-in test runner. Please follow the guidelines below.
|
|
6
|
+
|
|
7
|
+
## Development setup
|
|
8
|
+
|
|
9
|
+
- Prerequisites: Node 20.x, npm 10+.
|
|
10
|
+
- Install dependencies:
|
|
11
|
+
```sh
|
|
12
|
+
npm ci
|
|
13
|
+
```
|
|
14
|
+
- Type-check build (no emit by default):
|
|
15
|
+
```sh
|
|
16
|
+
npm run build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Coding standards
|
|
20
|
+
|
|
21
|
+
- TypeScript strict mode (via `@tsconfig/node20`).
|
|
22
|
+
- Line endings: CRLF (Windows) per tsconfig. Configure your editor to use CRLF to avoid noisy diffs.
|
|
23
|
+
- Linting: ESLint flat config (`eslint.config.mjs`) with `@eslint/js`, `typescript-eslint`, `eslint-plugin-unicorn`, and `eslint-plugin-promise`.
|
|
24
|
+
- Run manually:
|
|
25
|
+
```sh
|
|
26
|
+
npx eslint .
|
|
27
|
+
```
|
|
28
|
+
- Formatting: Prettier is available; run manually when needed:
|
|
29
|
+
```sh
|
|
30
|
+
npx prettier --write .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
|
|
35
|
+
- Framework: Node’s built-in test runner (`node:test`) with `node:assert/strict`).
|
|
36
|
+
- Default script runs tests under `test/`:
|
|
37
|
+
```sh
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
- Keep unit tests deterministic and offline by default.
|
|
41
|
+
- Avoid network I/O (Pub/Sub, AMQP) in tests; omit `topic`, `exchange`, or `queue` attributes, or stub `GcfCommon.response` in tests.
|
|
42
|
+
- Prefer JS tests under `test/*.test.js` so they run without transpilation.
|
|
43
|
+
- Optional: to run TypeScript tests, use a loader (e.g., `tsx`) or compile first. See `.junie/guidelines.md` for options.
|
|
44
|
+
|
|
45
|
+
## Commit messages and PRs
|
|
46
|
+
|
|
47
|
+
- Keep changes small and focused. Reference any related issue in the PR description.
|
|
48
|
+
- Include rationale in code comments for non-obvious behavior.
|
|
49
|
+
- Ensure `npm run build` passes and local tests are green.
|
|
50
|
+
|
|
51
|
+
## Release process
|
|
52
|
+
|
|
53
|
+
- Follow semantic versioning (semver). Document notable changes in the README and/or CHANGELOG when applicable.
|
|
54
|
+
- Publishing requires that metadata (description, keywords, license) is set and tests/type-check pass.
|
|
55
|
+
|
|
56
|
+
## Security
|
|
57
|
+
|
|
58
|
+
- Do not include secrets in the repository or logs. Prefer environment variables for configuration.
|
|
59
|
+
|
|
60
|
+
Thanks for contributing!
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 gcf-common maintainers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,110 @@
|
|
|
1
|
-
# gcf-common
|
|
1
|
+
# gcf-common-lib
|
|
2
|
+
|
|
3
|
+
Common helpers for Google Cloud Functions (GCF) and Node services:
|
|
4
|
+
- Orchestrate handlers and conditional response publishing via Pub/Sub or AMQP (RabbitMQ).
|
|
5
|
+
- Utilities: time helpers (ms/sec), Excel A1 conversions, safeJsonParse, simple delay/timeout.
|
|
6
|
+
- Helpers for AMQP and MongoDB lifecycles.
|
|
7
|
+
|
|
8
|
+
This library targets Node >= 20 and TypeScript. It is designed to be consumed by TS-aware builds or transpiled output.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm i gcf-common-lib
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
### Process an event without network I/O
|
|
19
|
+
Pass a payload without `topic`, `exchange`, or `queue` to avoid publishing in unit tests or offline flows.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { GcfCommon } from 'gcf-common-lib';
|
|
23
|
+
|
|
24
|
+
export async function handler(payload: any) {
|
|
25
|
+
return { ok: 1 };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Example payload: Pub/Sub-like message without routing attributes
|
|
29
|
+
const payload = {
|
|
30
|
+
event: { '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', json: { hello: 'world' }, attributes: {} },
|
|
31
|
+
context: undefined,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
await GcfCommon.process(payload, handler);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Publish a response to Pub/Sub
|
|
38
|
+
Set the `topic` attribute in metadata/attributes. Response attributes are coerced to strings; common fields `request_id`, `consumer_id`, `app_id`, `env` are propagated if present.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { GcfCommon } from 'gcf-common-lib';
|
|
42
|
+
|
|
43
|
+
const payload = {
|
|
44
|
+
event: { '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', json: {}, attributes: { topic: 'projects/my-proj/topics/my-topic', request_id: 'r1' } },
|
|
45
|
+
context: undefined,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await GcfCommon.process(payload, async () => ({ result: 42 }));
|
|
49
|
+
// GcfCommon.response() will publish to the provided topic
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Publish a response to AMQP (RabbitMQ)
|
|
53
|
+
Provide either `exchange` (with optional `queue` as routing key) or a `queue`.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { GcfCommon } from 'gcf-common-lib';
|
|
57
|
+
|
|
58
|
+
GcfCommon.amqpOptions.url = 'amqp://guest:guest@localhost:5672';
|
|
59
|
+
|
|
60
|
+
const payload = {
|
|
61
|
+
event: { '@type': 'type.googleapis.com/google.pubsub.v1.PubsubMessage', json: {}, attributes: { exchange: 'events', queue: 'route.key', request_id: 'r1' } },
|
|
62
|
+
context: undefined,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await GcfCommon.process(payload, async () => ({ result: 42 }));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API overview
|
|
69
|
+
|
|
70
|
+
### GcfCommon
|
|
71
|
+
- `process<TResponse, E = TEvent>(payload: TPayload<E>, handler: (p: TPayload<E>) => Promise<any> | any): Promise<any>`
|
|
72
|
+
- Runs handler, then calls `response(...)` with handler result; on error publishes a structured error via `buildResponse(error)` and rethrows.
|
|
73
|
+
- `response<E = TEvent>(payload: TPayload<E>, json?: TResponse, attributes?: Dict<any>): Promise<void>`
|
|
74
|
+
- Publishes to Pub/Sub if `topic` is present; to AMQP if `exchange` or `queue` is present. Attributes are stringified.
|
|
75
|
+
- `buildResponse(error: Error): TResponse`
|
|
76
|
+
- `getOptions(payload: TPayload): Promise<Dict<any>>`
|
|
77
|
+
- Parses JSON from `options` attribute with `safeJsonParse` and `{}` fallback.
|
|
78
|
+
- `getMetadataOrAttribute<E = TEvent>(payload: TPayload<E>): TMetadataOrAttributes`
|
|
79
|
+
- Extracts attributes from GCS finalize, Pub/Sub publish events, or HTTP request body shape.
|
|
80
|
+
- `getRequestMessage(request: express.Request)`
|
|
81
|
+
|
|
82
|
+
### Utils
|
|
83
|
+
- `ms({ w?, d?, h?, m?, s? }): number`
|
|
84
|
+
- `sec({ w?, d?, h?, m?, s? }): number`
|
|
85
|
+
- `indexToA1(idx: number): string`
|
|
86
|
+
- `A1ToIndex(value: string): number`
|
|
87
|
+
- `colNumToA1(columnNumber: number): string`
|
|
88
|
+
- `A1ToColNum(value: string): number`
|
|
89
|
+
- `safeJsonParse<T>(value: string, fallback?: T): T | undefined`
|
|
90
|
+
- `delay(seconds: number): Promise<void>`
|
|
91
|
+
- `timeoutAfter(seconds?: number): Promise<void>`
|
|
92
|
+
|
|
93
|
+
### AmqpHelper
|
|
94
|
+
- `withAmqpConn(fn, url)` — acquire AMQP connection (Bluebird disposer) and run `fn`.
|
|
95
|
+
- `withAmqpCh(fn, url, useConfirmChannel = false, prefetch = 1)` — channel lifecycle helper.
|
|
96
|
+
- `publishAmqp(ch, exchange | undefined, routingKey, json, options?)`
|
|
97
|
+
- `sendToQueueConfAmqp(ch: ConfirmChannel, queue, json, options?)`
|
|
98
|
+
|
|
99
|
+
### MongoHelper
|
|
100
|
+
- `collectionExists(client, name)`
|
|
101
|
+
- `collectionSafeGet(client, name, options?, afterCreate?)`
|
|
102
|
+
- `withMongoClient(fn, url, options?)` — maintains a shared client in-process.
|
|
103
|
+
|
|
104
|
+
## Testing
|
|
105
|
+
- Uses Node’s built-in test runner (`node:test`).
|
|
106
|
+
- Prefer JS tests under `test/*.test.js` for zero-transpilation.
|
|
107
|
+
- Avoid network I/O in unit tests by omitting `topic`, `exchange`, and `queue` attributes or by stubbing `GcfCommon.response`.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
MIT. See LICENSE.
|
package/docs/plan.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# gcf-common-lib: Improvement Plan
|
|
2
|
+
|
|
3
|
+
Last updated: 2025-08-16
|
|
4
|
+
Owner: gcf-common maintainers
|
|
5
|
+
|
|
6
|
+
## Purpose and Scope
|
|
7
|
+
This plan translates docs/tasks.md into an actionable roadmap. It sequences work to minimize risk: start with repo hygiene and tests, then architectural refactors, followed by reliability/observability and CI. It is grounded in the current repository state:
|
|
8
|
+
- Node >= 20, TypeScript ^5.3, ESLint flat config; CRLF line endings.
|
|
9
|
+
- tsconfig extends @tsconfig/node20; build does type-checking only (noEmit typical).
|
|
10
|
+
- package.json: main points to "src/index" (TS), no exports/types mapping, minimal scripts, empty license/description.
|
|
11
|
+
- README is nearly empty.
|
|
12
|
+
- GcfCommon in src/index.ts handles orchestration plus direct Pub/Sub/AMQP publishing; commented-out RxJS timeout and safeGetAttributes exist.
|
|
13
|
+
- utils.ts provides pure helpers (ms, sec, A1 conversions, safeJsonParse, timeoutAfter, delay).
|
|
14
|
+
- test/test.ts is TS and triggers network I/O (sets topic=\"test\"); Node cannot run TS tests without a loader by default.
|
|
15
|
+
|
|
16
|
+
## Guiding Goals and Constraints
|
|
17
|
+
- Keep unit tests deterministic and offline by default; avoid network calls unless explicitly enabled.
|
|
18
|
+
- Support Node 20+ and strict TypeScript; maintain CRLF to avoid noisy diffs.
|
|
19
|
+
- Favor small, incremental changes; document behavioral intent and breaking changes.
|
|
20
|
+
- Distribution decision must be explicit: TS-only consumers vs emitted JS.
|
|
21
|
+
|
|
22
|
+
## Phased Delivery (Recommended)
|
|
23
|
+
- Phase 0 (Hygiene): Documentation, metadata, scripts, deterministic tests for utilities and core flows.
|
|
24
|
+
- Phase 1 (Architecture): Extract transports and metadata parsing; remove/comment legacy code; introduce timeout mechanics.
|
|
25
|
+
- Phase 2 (Reliability & Observability): Retries/backoff, minimal logger, validation, security/config centralization.
|
|
26
|
+
- Phase 3 (DX & CI): Lint/format, examples, CI workflow, versioning/release.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1) Documentation and Repository Metadata
|
|
31
|
+
Rationale: Clear docs and metadata reduce onboarding friction and prevent misuse. Current README and package.json lack essential information.
|
|
32
|
+
Actions:
|
|
33
|
+
- Expand README: overview, install, quick start (Pub/Sub + AMQP flows), examples for GcfCommon.process and response behavior.
|
|
34
|
+
- API docs: brief signatures/examples for GcfCommon, AmqpHelper, MongoHelper, utils.
|
|
35
|
+
- Add CONTRIBUTING.md (dev setup, coding standards, testing strategy, release process) and optionally CODE_OF_CONDUCT.md.
|
|
36
|
+
- Set SPDX license in package.json and add LICENSE file.
|
|
37
|
+
- Fill package.json description and keywords.
|
|
38
|
+
Acceptance Criteria:
|
|
39
|
+
- README contains clear quick start and usage examples; package metadata is complete; API sections outline major functions with examples.
|
|
40
|
+
Risks/Mitigations:
|
|
41
|
+
- API drift: Automate with simple typedoc or hand-maintained JSDoc; keep short examples stable.
|
|
42
|
+
|
|
43
|
+
## 2) Testing Strategy and Coverage
|
|
44
|
+
Rationale: Current tests are TS and trigger network I/O. Node test runner can run JS tests without loaders; pure utilities are ideal.
|
|
45
|
+
Actions:
|
|
46
|
+
- Default runnable tests (no transpilation): add JS tests under test/*.test.js for utils (ms, sec, A1 conversions, safeJsonParse) and core GcfCommon flows without network I/O.
|
|
47
|
+
- Gate network tests behind env flags (e.g., INTEGRATION_PUBSUB=1, INTEGRATION_AMQP=1) and skip by default in CI.
|
|
48
|
+
- Add unit tests for GcfCommon.process that avoid network I/O (omit topic/exchange/queue) including error path and buildResponse.
|
|
49
|
+
- Add tests for getMetadataOrAttribute across GCS finalize, Pub/Sub publish, and HTTP request payloads (using request body shape).
|
|
50
|
+
- Optional TS tests: if kept, add loader (tsx) and npm script: "test:ts": "node --test --import tsx .\\test\\**/*.ts".
|
|
51
|
+
- Add c8 for coverage and script: "test:coverage".
|
|
52
|
+
Acceptance Criteria:
|
|
53
|
+
- Running `npm test` executes JS tests successfully without external services.
|
|
54
|
+
- Coverage report generated by `npm run test:coverage`; core branches in GcfCommon and utils are covered.
|
|
55
|
+
Risks/Mitigations:
|
|
56
|
+
- Flaky network tests: disabled by default; run only with explicit env flags.
|
|
57
|
+
|
|
58
|
+
## 3) Build and Packaging
|
|
59
|
+
Rationale: package.json points to TS entry; consumers without TS loaders may fail. Decision needed for distribution model.
|
|
60
|
+
Decision Path:
|
|
61
|
+
- Option A (TS-only, default recommendation short-term):
|
|
62
|
+
- Add "types": "src/index.d.ts" (or rely on TS source types), and an "exports" map that points to source for TS-aware builds. Document that consumers need TS transpilation.
|
|
63
|
+
- Option B (Emit JS):
|
|
64
|
+
- Add tsconfig.build.json (noEmit: false, outDir: dist). Update scripts: build, clean, prepare. Add exports/types pointing to dist.
|
|
65
|
+
- Validate CJS/ESM interop.
|
|
66
|
+
Acceptance Criteria:
|
|
67
|
+
- Readme states model explicitly; package.json has correct fields; a simple consumer setup works for both ESM/CJS.
|
|
68
|
+
Risks/Mitigations:
|
|
69
|
+
- Breaking resolution for existing consumers: publish as minor with clear note; provide migration guide.
|
|
70
|
+
|
|
71
|
+
## 4) Architectural Separation of Concerns
|
|
72
|
+
Rationale: GcfCommon.response mixes transport concerns; separation improves testability and reliability features (retries, backoff).
|
|
73
|
+
Actions:
|
|
74
|
+
- Extract PubSubTransport and AmqpTransport implementing a common interface: publish(payload, json, attributes, context).
|
|
75
|
+
- Extract metadata/attributes parsing into a utility with explicit type guards; unify attribute coercion to strings.
|
|
76
|
+
- Keep GcfCommon.process focused on orchestration (handler, timeout, and delegating response publishing).
|
|
77
|
+
Acceptance Criteria:
|
|
78
|
+
- response() delegates to transport(s) selected by metadata; core orchestration has no direct SDK calls.
|
|
79
|
+
Risks/Mitigations:
|
|
80
|
+
- Refactor risk: Introduce transports behind current API; maintain functional parity first.
|
|
81
|
+
|
|
82
|
+
## 5) Reliability: Retries, Timeouts, Backoff
|
|
83
|
+
Rationale: Network calls fail transiently; timeouts prevent unbounded work.
|
|
84
|
+
Actions:
|
|
85
|
+
- Implement configurable retries with exponential backoff and jitter for Pub/Sub and AMQP publishes (max attempts, base delay).
|
|
86
|
+
- Add configurable timeout to GcfCommon.process using AbortController or Promise.race. Remove/comment the RxJS approach entirely or guard behind feature flag.
|
|
87
|
+
- Document idempotency: propagate correlationId/request_id; ensure publish options set correlationId where applicable.
|
|
88
|
+
Acceptance Criteria:
|
|
89
|
+
- Transient publish failures are retried; process() can be configured to timeout; RxJS code is removed or feature-flagged.
|
|
90
|
+
|
|
91
|
+
## 6) Logging and Observability
|
|
92
|
+
Rationale: console.log is noisy and unstructured.
|
|
93
|
+
Actions:
|
|
94
|
+
- Introduce minimal logger interface with levels (debug/info/warn/error) and structured context (request_id, app_id, env).
|
|
95
|
+
- Add debug logs around metadata extraction and transport selection; redact sensitive fields.
|
|
96
|
+
- Provide hooks to plug external loggers (e.g., pass in a logger or set a factory) without hard dependency.
|
|
97
|
+
Acceptance Criteria:
|
|
98
|
+
- No direct console.log in core paths; structured logs present; redaction rules applied.
|
|
99
|
+
|
|
100
|
+
## 7) Input Validation and Typing
|
|
101
|
+
Rationale: Inputs come from varied sources (Pub/Sub, GCS, HTTP) and may be ill-formed.
|
|
102
|
+
Actions:
|
|
103
|
+
- Validate/normalize metadata/attributes (ensure strings; verify topic/exchange/queue formats).
|
|
104
|
+
- Make getOptions generic: getOptions<T = Dict<any>>(...): Promise<T> with optional schema (e.g., zod) for runtime validation; still use safeJsonParse fallback.
|
|
105
|
+
- Tighten TResponse typing; mark readonly where applicable; optionally add branded identifiers (RequestId, ConsumerId).
|
|
106
|
+
Acceptance Criteria:
|
|
107
|
+
- Invalid metadata is rejected or sanitized; getOptions infers type with optional runtime validation.
|
|
108
|
+
|
|
109
|
+
## 8) AMQP Helper Modernization
|
|
110
|
+
Rationale: Modern async/await patterns improve clarity; configuration should be validated.
|
|
111
|
+
Actions:
|
|
112
|
+
- Replace Bluebird disposers with async/await and try/finally (retain confirm channel variant as needed).
|
|
113
|
+
- Validate connection URL and emit clear error when amqpOptions.url is missing.
|
|
114
|
+
- Support configurable prefetch and confirm mode in options.
|
|
115
|
+
- Implement publish backpressure handling and retries with reconnection on channel/connection errors.
|
|
116
|
+
Acceptance Criteria:
|
|
117
|
+
- AmqpHelper uses async/await; fails fast on missing URL; supports prefetch/confirm; has retry/reconnect logic.
|
|
118
|
+
|
|
119
|
+
## 9) Mongo Client Lifecycle and Locking
|
|
120
|
+
Rationale: Avoid global leaks; ensure resilience.
|
|
121
|
+
Actions:
|
|
122
|
+
- Review MongoHelper.withMongoClient; manage a shared client with health checks or expose a close method.
|
|
123
|
+
- Add ping/healthcheck and transient error handling.
|
|
124
|
+
- Deprecate MongoLock formally (annotate, document alternative using findOneAndUpdate with TTL index).
|
|
125
|
+
Acceptance Criteria:
|
|
126
|
+
- Predictable client lifecycle; documented locking alternative; healthcheck passes under transient faults.
|
|
127
|
+
|
|
128
|
+
## 10) Dead Code and Comments Cleanup
|
|
129
|
+
Rationale: Commented code increases confusion.
|
|
130
|
+
Actions:
|
|
131
|
+
- Remove or finalize the commented RxJS timeout code in GcfCommon.
|
|
132
|
+
- Remove the commented safeGetAttributes path or implement a proper version that fetches Storage metadata when needed.
|
|
133
|
+
- Add JSDoc for public APIs and rationale for non-obvious behavior.
|
|
134
|
+
Acceptance Criteria:
|
|
135
|
+
- No stale commented blocks; public APIs carry JSDoc.
|
|
136
|
+
|
|
137
|
+
## 11) Pub/Sub Specifics and Payload Handling
|
|
138
|
+
Rationale: Attribute types and size limits matter for reliability.
|
|
139
|
+
Actions:
|
|
140
|
+
- Validate attribute sizes and JSON payload limits; document constraints.
|
|
141
|
+
- Consider enabling message ordering; document how to set ordering keys.
|
|
142
|
+
- Ensure attributes are consistently strings; add a helper for coercion (already partially in response()).
|
|
143
|
+
Acceptance Criteria:
|
|
144
|
+
- Publishing respects constraints; ordering documented; attributes normalized.
|
|
145
|
+
|
|
146
|
+
## 12) Security and Configuration
|
|
147
|
+
Rationale: Sensitive config should be centralized and redacted in logs.
|
|
148
|
+
Actions:
|
|
149
|
+
- Centralize configuration (AMQP URL, Pub/Sub defaults) with environment variable support and typed config loader.
|
|
150
|
+
- Redact sensitive fields in logs and propagate only safe metadata to responses.
|
|
151
|
+
- Run `npm audit` regularly; schedule weekly dependency update checks.
|
|
152
|
+
Acceptance Criteria:
|
|
153
|
+
- Single config source with types; logs redact secrets; audit script/process documented.
|
|
154
|
+
|
|
155
|
+
## 13) Linting, Formatting, and Scripts
|
|
156
|
+
Rationale: Consistency and quick feedback loops.
|
|
157
|
+
Actions:
|
|
158
|
+
- Add npm scripts: "lint", "lint:fix", "format"; ensure Prettier integration.
|
|
159
|
+
- Consider enabling additional ESLint rules (promise rules, no-floating-promises) where pragmatic.
|
|
160
|
+
- Add .editorconfig to enforce CRLF and common conventions.
|
|
161
|
+
Acceptance Criteria:
|
|
162
|
+
- Lint and format run clean; repo observes CRLF and style conventions.
|
|
163
|
+
|
|
164
|
+
## 14) Continuous Integration
|
|
165
|
+
Rationale: Automated checks ensure quality across environments.
|
|
166
|
+
Actions:
|
|
167
|
+
- Add GitHub Actions workflow (Node 20.x) to run lint, type-check, and unit tests (JS tests only by default). Cache npm and enable problem matchers.
|
|
168
|
+
Acceptance Criteria:
|
|
169
|
+
- CI runs on PRs and main; caches dependencies; surfaces lint/type/test results quickly.
|
|
170
|
+
|
|
171
|
+
## 15) Examples and Developer Experience
|
|
172
|
+
Rationale: Concrete examples accelerate adoption and testing.
|
|
173
|
+
Actions:
|
|
174
|
+
- Add examples/ with a minimal handler using GcfCommon.process showing both no-network and networked responses.
|
|
175
|
+
- Document how to stub response publishing in tests; show amqpOptions runtime configuration.
|
|
176
|
+
Acceptance Criteria:
|
|
177
|
+
- Copy-pasteable examples work locally; docs describe stubbing approach and config patterns.
|
|
178
|
+
|
|
179
|
+
## 16) Versioning and Release Process
|
|
180
|
+
Rationale: Predictable releases and migration guidance.
|
|
181
|
+
Actions:
|
|
182
|
+
- Adopt semantic versioning explicitly; add CHANGELOG and automate release notes (GitHub Releases).
|
|
183
|
+
- Document any breaking changes from architectural refactors and provide migration steps.
|
|
184
|
+
Acceptance Criteria:
|
|
185
|
+
- CHANGELOG maintained; releases include notes; migration guides exist for breaking changes.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Prioritized Backlog (First Pass)
|
|
190
|
+
1) Phase 0
|
|
191
|
+
- README overhaul; package.json metadata; LICENSE.
|
|
192
|
+
- JS unit tests for utils and GcfCommon.process (no network). Add c8 coverage.
|
|
193
|
+
- Add npm scripts: lint, format; ensure ESLint/Prettier run.
|
|
194
|
+
|
|
195
|
+
2) Phase 1
|
|
196
|
+
- Extract transports (PubSubTransport, AmqpTransport) and metadata parsing utility.
|
|
197
|
+
- Implement process() timeout (AbortController/Promise.race). Remove RxJS commented code.
|
|
198
|
+
|
|
199
|
+
3) Phase 2
|
|
200
|
+
- Retry/backoff with jitter for publishes; attribute coercion helper; input validation.
|
|
201
|
+
- Minimal logger and redaction; central config loader.
|
|
202
|
+
|
|
203
|
+
4) Phase 3
|
|
204
|
+
- CI workflow; examples/; decide and implement distribution model (TS-only short-term or emit JS) and document.
|
|
205
|
+
- Versioning: CHANGELOG and release notes automation.
|
|
206
|
+
|
|
207
|
+
## Acceptance Gates per Phase
|
|
208
|
+
- Phase 0 Gate: `npm test` passes offline; README explains usage; coverage report available; lint/format scripts present.
|
|
209
|
+
- Phase 1 Gate: response() delegates to transports; process() timeout configurable; no stale commented code.
|
|
210
|
+
- Phase 2 Gate: retries and validation in place; structured logging with redaction; config centralized.
|
|
211
|
+
- Phase 3 Gate: CI green on PRs; examples runnable; packaging decision implemented and documented; release notes process live.
|