@openapi-typescript-infra/service 6.10.1 → 6.10.2

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.
Files changed (48) hide show
  1. package/package.json +8 -2
  2. package/.github/workflows/codeql-analysis.yml +0 -77
  3. package/.github/workflows/nodejs.yml +0 -62
  4. package/.trunk/configs/.markdownlint.yaml +0 -10
  5. package/.trunk/configs/.yamllint.yaml +0 -10
  6. package/.trunk/trunk.yaml +0 -35
  7. package/.yarn/patches/confit-npm-3.0.0-eade8c7ce1.patch +0 -52
  8. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +0 -541
  9. package/.yarn/releases/yarn-3.2.3.cjs +0 -783
  10. package/.yarnrc.yml +0 -7
  11. package/CHANGELOG.md +0 -525
  12. package/SECURITY.md +0 -12
  13. package/__tests__/config.test.ts +0 -53
  14. package/__tests__/fake-serv/api/fake-serv.yaml +0 -48
  15. package/__tests__/fake-serv/config/config.json +0 -13
  16. package/__tests__/fake-serv/src/handlers/hello.ts +0 -17
  17. package/__tests__/fake-serv/src/index.ts +0 -36
  18. package/__tests__/fake-serv/src/routes/error.ts +0 -16
  19. package/__tests__/fake-serv/src/routes/index.ts +0 -19
  20. package/__tests__/fake-serv/src/routes/other/world.ts +0 -7
  21. package/__tests__/fake-serv.test.ts +0 -119
  22. package/__tests__/vitest.test-setup.ts +0 -15
  23. package/src/bin/start-service.ts +0 -32
  24. package/src/bootstrap.ts +0 -160
  25. package/src/config/index.ts +0 -124
  26. package/src/config/schema.ts +0 -70
  27. package/src/config/shortstops.ts +0 -155
  28. package/src/config/validation.ts +0 -23
  29. package/src/development/port-finder.ts +0 -67
  30. package/src/development/repl.ts +0 -131
  31. package/src/env.ts +0 -29
  32. package/src/error.ts +0 -47
  33. package/src/express-app/app.ts +0 -438
  34. package/src/express-app/index.ts +0 -3
  35. package/src/express-app/internal-server.ts +0 -43
  36. package/src/express-app/modules.ts +0 -10
  37. package/src/express-app/route-loader.ts +0 -40
  38. package/src/express-app/types.ts +0 -32
  39. package/src/hook.ts +0 -36
  40. package/src/index.ts +0 -9
  41. package/src/openapi.ts +0 -184
  42. package/src/telemetry/DummyExporter.ts +0 -17
  43. package/src/telemetry/hook-modules.ts +0 -8
  44. package/src/telemetry/index.ts +0 -168
  45. package/src/telemetry/instrumentations.ts +0 -103
  46. package/src/telemetry/requestLogger.ts +0 -267
  47. package/src/tsx.d.ts +0 -1
  48. package/src/types.ts +0 -223
@@ -1,155 +0,0 @@
1
- import os from 'os';
2
- import path from 'path';
3
-
4
- import {
5
- base64Handler,
6
- envHandler,
7
- fileHandler,
8
- pathHandler,
9
- requireHandler,
10
- yamlHandler,
11
- type ShortstopHandler,
12
- } from '@sesamecare-oss/confit';
13
-
14
- /**
15
- * Default shortstop handlers for GasBuddy service configuration
16
- */
17
-
18
- /**
19
- * A require: shortstop that will dig and find a named function
20
- * with a url-like hash pattern
21
- */
22
- function betterRequire(basepath: string) {
23
- const baseRequire = requireHandler(basepath);
24
- return function hashRequire(v: string) {
25
- const [moduleName, func] = v.split('#');
26
- const module = baseRequire(moduleName);
27
- if (func) {
28
- if (module[func]) {
29
- return module[func];
30
- }
31
- return baseRequire(v);
32
- }
33
- return module;
34
- };
35
- }
36
-
37
- /**
38
- * Just like path, but resolve ~/ to the home directory
39
- */
40
- function betterPath(basepath: string) {
41
- const basePath = pathHandler(basepath);
42
- return function pathWithHomeDir(v: string) {
43
- if (v.startsWith('~/')) {
44
- return basePath(path.join(os.homedir(), v.slice(2)));
45
- }
46
- return basePath(v);
47
- };
48
- }
49
-
50
- /**
51
- * Just like file, but resolve ~/ to the home directory
52
- */
53
- function betterFile(basepath: string) {
54
- const baseFile = fileHandler(basepath);
55
- return function fileWithHomeDir(v: string) {
56
- if (v.startsWith('~/')) {
57
- return baseFile(path.join(os.homedir(), v.slice(2)));
58
- }
59
- return baseFile(v);
60
- };
61
- }
62
-
63
- function canonicalizeServiceSuffix(suffix?: string) {
64
- if (!suffix) {
65
- return 'internal';
66
- }
67
- return { serv: 'internal' }[suffix] || suffix;
68
- }
69
-
70
- /**
71
- * Our convention is that service names end with:
72
- * -serv or -internal - a back end service not callable by the outside world and where no authorization occurs (canonicalized to internal)
73
- * -api - a non-UI front end service that exposes swagger and sometimes non-swagger APIs
74
- * -web - a UI front end service
75
- * -worker - a scheduled job or queue processor
76
- *
77
- * This shortstop will take a CSV of service types and tell you if this service is
78
- * of that type, or if the first character after serviceType: is an exclamation point,
79
- * whether it's NOT of any of the specified types
80
- */
81
- function serviceTypeFactory(name: string) {
82
- const type = canonicalizeServiceSuffix(name.split('-').pop());
83
-
84
- return function serviceType(v: string) {
85
- let checkValue = v;
86
- let matchIsGood = true;
87
- if (checkValue.startsWith('!')) {
88
- matchIsGood = false;
89
- checkValue = checkValue.substring(1);
90
- }
91
- const values = checkValue.split(',').map(canonicalizeServiceSuffix);
92
- // Welp, there's no XOR so here we are.
93
- return type && values.includes(type) ? matchIsGood : !matchIsGood;
94
- };
95
- }
96
-
97
- const osMethods = {
98
- hostname: os.hostname,
99
- platform: os.platform,
100
- type: os.type,
101
- version: os.version,
102
- };
103
-
104
- export function shortstops(service: { name: string }, sourcedir: string) {
105
- /**
106
- * Since we use transpiled sources a lot,
107
- * basedir and sourcedir are meaningfully different reference points.
108
- */
109
- const basedir = path.join(sourcedir, '..');
110
-
111
- const env = envHandler();
112
-
113
- return {
114
- env,
115
- // A version of env that can default to false
116
- env_switch(v: string) {
117
- if (v && v.startsWith('!')) {
118
- const bval = env(`${v.substring(1)}|b`);
119
- return !bval;
120
- }
121
- return !!env(v);
122
- },
123
- base64: base64Handler(),
124
- regex(v: string) {
125
- const [, pattern, flags] = /^\/(.*)\/([a-z]*)$/.exec(v) || [];
126
- if (pattern === undefined) {
127
- throw new Error(`Invalid regular expression in configuration ${v}`);
128
- }
129
- return new RegExp(pattern, flags);
130
- },
131
-
132
- // handle source and base directory intelligently
133
- path: betterPath(basedir),
134
- sourcepath: pathHandler(sourcedir),
135
- file: betterFile(basedir),
136
- sourcefile: fileHandler(sourcedir),
137
- require: betterRequire(basedir),
138
- sourcerequire: betterRequire(sourcedir),
139
-
140
- // Sometimes yaml is more pleasant for configuration
141
- yaml: yamlHandler(basedir),
142
-
143
- // Switch on service type
144
- servicetype: serviceTypeFactory(service.name),
145
- servicename: (v: string) => v.replace(/\$\{name\}/g, service.name),
146
-
147
- os(p: string) {
148
- return osMethods[p as keyof typeof osMethods]();
149
- },
150
- // No-op in case you have values that start with a shortstop handler name (and colon)
151
- literal(v: string) {
152
- return v;
153
- },
154
- } satisfies Record<string, ShortstopHandler<string, unknown>>;
155
- }
@@ -1,23 +0,0 @@
1
- import type { ConfigurationSchema } from './schema.js';
2
-
3
- export interface ConfigValidationError {
4
- path: string;
5
- message: string;
6
- }
7
-
8
- export type ConfigurationValidator<Config extends ConfigurationSchema> = (config: Config) => {
9
- success: boolean;
10
- errors: ConfigValidationError[];
11
- };
12
-
13
- export function validateConfiguration<Config extends ConfigurationSchema>(
14
- config: Config,
15
- validator: ConfigurationValidator<Config>,
16
- ) {
17
- const result = validator(config);
18
- if (!result.success) {
19
- const errorMessages = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join('\n');
20
- throw new Error(`Configuration validation failed:
21
- ${errorMessages}`);
22
- }
23
- }
@@ -1,67 +0,0 @@
1
- import net from 'net';
2
-
3
- import { isTest } from '../env.js';
4
-
5
- // Inspired by https://github.com/kessler/find-port/blob/master/lib/findPort.js
6
- async function isAvailable(port: number) {
7
- return new Promise((accept, reject) => {
8
- const server = net.createServer().listen(port);
9
-
10
- const timeoutRef = setTimeout(() => {
11
- accept(false);
12
- }, 2000);
13
-
14
- timeoutRef.unref();
15
-
16
- server.once('listening', () => {
17
- clearTimeout(timeoutRef);
18
- server.close();
19
- accept(true);
20
- });
21
- server.once('error', (err) => {
22
- clearTimeout(timeoutRef);
23
-
24
- if ((err as { code?: string }).code === 'EADDRINUSE') {
25
- accept(false);
26
- return;
27
- }
28
-
29
- reject(err);
30
- });
31
- });
32
- }
33
-
34
- async function findPort(start: number) {
35
- for (let p = start; p < start + 1000; p += 1) {
36
- if (await isAvailable(p)) {
37
- return p;
38
- }
39
- }
40
- return 0;
41
- }
42
-
43
- async function getEphemeralPort(): Promise<number> {
44
- return new Promise((resolve, reject) => {
45
- const server = net.createServer();
46
-
47
- server.listen(0, () => {
48
- const address = server.address();
49
- if (typeof address === 'string' || !address) {
50
- reject(new Error('Invalid address'));
51
- return;
52
- }
53
- const port = address.port; // Retrieve the ephemeral port
54
- server.close((err) => {
55
- if (err) {
56
- reject(err);
57
- } else {
58
- resolve(port);
59
- }
60
- });
61
- });
62
- });
63
- }
64
-
65
- export async function getAvailablePort(basePort: number): Promise<number> {
66
- return isTest() || process.env.TEST_RUNNER ? getEphemeralPort() : findPort(basePort);
67
- }
@@ -1,131 +0,0 @@
1
- import type { REPLServer } from 'node:repl';
2
- import repl from 'node:repl';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
-
6
- import { glob } from 'glob';
7
- import { set } from 'moderndash';
8
-
9
- import type { AnyServiceLocals, ServiceExpress, ServiceLocals } from '../types.js';
10
- import type { ConfigurationSchema } from '../config/schema.js';
11
-
12
- const REPL_PROP = '$$repl$$';
13
-
14
- interface WithReplProp {
15
- [REPL_PROP]?: string;
16
- }
17
-
18
- export function serviceRepl<SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>>(
19
- app: ServiceExpress<SLocals>,
20
- codepath: string | undefined,
21
- onExit: () => void,
22
- ) {
23
- class FakeReq {
24
- locals: { app: ServiceExpress<SLocals> } = { app };
25
- headers: Record<string, string | string[] | undefined> = {};
26
- query = new URLSearchParams();
27
- body: unknown = {};
28
-
29
- constructor(public path: string) {
30
- this.locals.app = app;
31
- }
32
- }
33
-
34
- const rl = repl.start({
35
- prompt: '> ',
36
- });
37
- Object.assign(rl.context, app.locals, {
38
- app,
39
- req: new FakeReq('/'),
40
- dump(o: unknown) {
41
- console.log(JSON.stringify(o, null, '\t'));
42
- },
43
- // Use iTerm2's escape code to copy to clipboard
44
- pbcopy(str: string) {
45
- const encoded = Buffer.from(str.toString(), 'utf8').toString('base64');
46
- process.stdout.write(`\x1b]52;c;${encoded}\x07`);
47
- },
48
- });
49
- rl.setupHistory(path.resolve('.node_repl_history'), (err) => {
50
- if (err) {
51
- // eslint-disable-next-line no-console
52
- console.error('History setup failed', err);
53
- }
54
- });
55
- app.locals.service.attachRepl?.(app, rl);
56
-
57
- loadReplFunctions(app, codepath, rl).catch((error) => {
58
- // eslint-disable-next-line no-console
59
- console.error('Failed to load REPL functions', error);
60
- });
61
-
62
- rl.on('exit', onExit);
63
- }
64
-
65
- async function loadReplFunctions<
66
- SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
67
- >(app: ServiceExpress<SLocals>, codepath: string | undefined, rl: REPLServer) {
68
- if (!codepath) {
69
- return;
70
- }
71
-
72
- const files = glob.sync(path.join(codepath, '**/*.{js,ts}'));
73
-
74
- for (const file of files) {
75
- try {
76
- // Read the file content as text
77
- const fileContent = fs.readFileSync(file, 'utf-8');
78
-
79
- // Check if repl$ is present, in a very rudimentary way (note built JS has close paren not open)
80
- if (/repl\$[()]/.test(fileContent)) {
81
- const module = await import(path.resolve(file));
82
-
83
- // Look for functions with the REPL_PROP marker
84
- for (const exported of Object.values(module as Record<string, unknown>)) {
85
- if (!exported) {
86
- continue;
87
- }
88
- if (typeof exported === 'function') {
89
- const replName = (exported as WithReplProp)[REPL_PROP];
90
- if (replName) {
91
- set(rl.context, replName, exported.bind(null, app));
92
- }
93
- }
94
- }
95
- }
96
- } catch (err) {
97
- // eslint-disable-next-line no-console
98
- console.error(`Failed to load REPL functions from ${file}:`, err);
99
- }
100
- }
101
- }
102
-
103
- // Can't seem to sort out proper generics here, so we'll just use any since it's dev only
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- type ReplAny = any;
106
-
107
- /**
108
- * This decorator-like function can be applied to functions and the service will load and expose
109
- * the function when the repl is engaged.
110
- *
111
- * async function myFunction(app: MyService['App'], arg1: string, arg2: number) {
112
- * }
113
- * repl$(myFunction);
114
- *
115
- * or
116
- *
117
- * repl(myFunction, 'some.func.name');
118
- */
119
- export function repl$<
120
- S extends ServiceExpress<ReplAny>,
121
- T extends (app: S, ...args: ReplAny[]) => ReplAny,
122
- >(fn: T, name?: string) {
123
- const functionName = name || fn.name;
124
- if (!functionName) {
125
- throw new Error('Function must have a name or a name must be provided.');
126
- }
127
- Object.defineProperty(fn, REPL_PROP, {
128
- enumerable: false,
129
- value: functionName,
130
- });
131
- }
package/src/env.ts DELETED
@@ -1,29 +0,0 @@
1
- type ValidEnv = 'development' | 'production' | 'staging' | 'test';
2
-
3
- export function getNodeEnv(): ValidEnv {
4
- switch (process.env.APP_ENV || process.env.NODE_ENV) {
5
- case 'production':
6
- case 'staging':
7
- case 'test':
8
- return (process.env.APP_ENV || process.env.NODE_ENV) as ValidEnv;
9
- case undefined:
10
- default:
11
- return 'development';
12
- }
13
- }
14
-
15
- export function isDev() {
16
- return getNodeEnv() === 'development';
17
- }
18
-
19
- export function isProd() {
20
- return getNodeEnv() === 'production';
21
- }
22
-
23
- export function isStaging() {
24
- return getNodeEnv() === 'staging';
25
- }
26
-
27
- export function isTest() {
28
- return getNodeEnv() === 'test';
29
- }
package/src/error.ts DELETED
@@ -1,47 +0,0 @@
1
- import type { ConfigurationSchema } from './config/schema.js';
2
- import type { AnyServiceLocals, ServiceLike, ServiceLocals } from './types.js';
3
-
4
- export interface ServiceErrorSpec {
5
- status?: number;
6
- code?: string;
7
- domain?: string;
8
- display_message?: string;
9
- log_stack?: boolean;
10
- expected_error?: boolean;
11
- client_metadata?: Record<string, unknown>;
12
- }
13
-
14
- /**
15
- * An error that gives more structured information to callers. Throw inside a handler as
16
- *
17
- * throw new Error(req, 'Something broke', { code: 'SomethingBroke', status: 400 });
18
- *
19
- * You can also include a display_message which is intended to be viewed by the end user
20
- */
21
- export class ServiceError<
22
- SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
23
- > extends Error {
24
- public status: number | undefined;
25
-
26
- public code?: string;
27
-
28
- public domain: string;
29
-
30
- public display_message?: string;
31
-
32
- public log_stack?: boolean;
33
-
34
- // Additional data TO BE RETURNED TO THE CLIENT
35
- public client_metadata?: Record<string, unknown>;
36
-
37
- // If true, this shouldn't be logged as an error, but as an info log.
38
- // This is common when the error needs to go to the client, but should not
39
- // take up the valuable mental space of an error log.
40
- public expected_error?: boolean;
41
-
42
- constructor(app: ServiceLike<SLocals>, message: string, spec?: ServiceErrorSpec) {
43
- super(message);
44
- this.domain = app.locals.name;
45
- Object.assign(this, spec);
46
- }
47
- }