@fastrack/core 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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/create-app.d.ts +19 -0
- package/dist/create-app.d.ts.map +1 -0
- package/dist/create-app.js +83 -0
- package/dist/create-app.js.map +1 -0
- package/dist/error.d.ts +20 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +30 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
- package/src/create-app.ts +102 -0
- package/src/error.ts +40 -0
- package/src/index.ts +7 -0
- package/src/types.ts +64 -0
- package/tsconfig.json +9 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fastrack
|
|
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
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @fastrack/core
|
|
2
|
+
|
|
3
|
+
Application bootstrap, starter loading, config aggregation and validation, standard error model, request context and correlation IDs, graceful shutdown and readiness toggle.
|
|
4
|
+
|
|
5
|
+
See [docs/architecture.md](../../docs/architecture.md) and the design document for the full platform vision.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @fastrack/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createApp } from "@fastrack/core";
|
|
17
|
+
import { webStarter } from "@fastrack/starter-web";
|
|
18
|
+
import { actuatorStarter } from "@fastrack/starter-actuator";
|
|
19
|
+
|
|
20
|
+
const app = await createApp({
|
|
21
|
+
starters: [webStarter, actuatorStarter],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await app.listen({ port: 3000 });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Env config (dotenv)
|
|
28
|
+
|
|
29
|
+
By default, `createApp` loads a `.env` file from the current working directory (via [dotenv](https://www.npmjs.com/package/dotenv)), so `process.env` is populated before config is built. You can then pass config from env or use it in starters:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const app = await createApp({
|
|
33
|
+
envPath: ".env", // default; set to false to disable
|
|
34
|
+
config: {
|
|
35
|
+
web: { port: Number(process.env.PORT) ?? 3000 },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Set `envPath: false` to disable loading a .env file.
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
- `createApp(options)` — Bootstrap Fastify with starters and config.
|
|
45
|
+
- `FastrackStarter<TConfig>` — Starter interface (name, configSchema, register).
|
|
46
|
+
- `FastrackContext` — Bootstrap context passed to starters.
|
|
47
|
+
- Problem-details error schema and `replyError` helper.
|
|
48
|
+
- Readiness getter/setter for actuator integration.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { FastrackContext, CreateAppOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a Fastrack app: load env (.env via dotenv), validate starter configs, register starters, set up shutdown.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createApp(options?: CreateAppOptions): Promise<FastifyInstance>;
|
|
7
|
+
/**
|
|
8
|
+
* Get the Fastrack context from an app (for actuator / other starters).
|
|
9
|
+
*/
|
|
10
|
+
export declare function getFastrackContext(app: FastifyInstance): FastrackContext | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Get health contributors map from app (for actuator).
|
|
13
|
+
*/
|
|
14
|
+
export declare function getHealthContributors(app: FastifyInstance): Map<string, () => Promise<{
|
|
15
|
+
ok: boolean;
|
|
16
|
+
status?: "up" | "down";
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
}>> | undefined;
|
|
19
|
+
//# sourceMappingURL=create-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-app.d.ts","sourceRoot":"","sources":["../src/create-app.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIpE;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CA0ExF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,eAAe,GAAG,SAAS,CAEpF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,GACnB,GAAG,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,GAAG,SAAS,CAEpH"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { config as dotenvConfig } from "dotenv";
|
|
2
|
+
import { Value } from "@sinclair/typebox/value";
|
|
3
|
+
import Fastify from "fastify";
|
|
4
|
+
const DEFAULT_SHUTDOWN_MS = 30_000;
|
|
5
|
+
/**
|
|
6
|
+
* Create a Fastrack app: load env (.env via dotenv), validate starter configs, register starters, set up shutdown.
|
|
7
|
+
*/
|
|
8
|
+
export async function createApp(options = {}) {
|
|
9
|
+
const { starters = [], config: configOverride = {}, envPath = ".env", shutdownTimeoutMs = DEFAULT_SHUTDOWN_MS, } = options;
|
|
10
|
+
if (envPath !== false) {
|
|
11
|
+
dotenvConfig({ path: envPath });
|
|
12
|
+
}
|
|
13
|
+
const app = Fastify({ logger: true });
|
|
14
|
+
let readiness = true;
|
|
15
|
+
const shutdownHooks = [];
|
|
16
|
+
const healthContributors = new Map();
|
|
17
|
+
const ctx = {
|
|
18
|
+
app,
|
|
19
|
+
config: { ...configOverride },
|
|
20
|
+
get logger() {
|
|
21
|
+
return app.log;
|
|
22
|
+
},
|
|
23
|
+
getReadiness: () => readiness,
|
|
24
|
+
setReadiness: (ready) => {
|
|
25
|
+
readiness = ready;
|
|
26
|
+
},
|
|
27
|
+
onShutdown: (fn) => {
|
|
28
|
+
shutdownHooks.push(fn);
|
|
29
|
+
},
|
|
30
|
+
registerHealthContributor: (name, check) => {
|
|
31
|
+
healthContributors.set(name, check);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
for (const starter of starters) {
|
|
35
|
+
const defaults = starter.defaults ?? {};
|
|
36
|
+
const raw = ctx.config[starter.name] ?? {};
|
|
37
|
+
const merged = { ...defaults, ...raw };
|
|
38
|
+
const errors = [...Value.Errors(starter.configSchema, merged)];
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
const msg = errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
41
|
+
throw new Error(`Config validation failed for starter "${starter.name}": ${msg}`);
|
|
42
|
+
}
|
|
43
|
+
const result = Value.Decode(starter.configSchema, merged);
|
|
44
|
+
await Promise.resolve(starter.register(app, ctx, result));
|
|
45
|
+
}
|
|
46
|
+
const shutdown = async (signal) => {
|
|
47
|
+
readiness = false;
|
|
48
|
+
app.log?.info?.({ signal }, "Shutdown: readiness=false");
|
|
49
|
+
const timeout = setTimeout(() => {
|
|
50
|
+
app.log?.error?.("Shutdown timeout; exiting");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}, shutdownTimeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
for (let i = shutdownHooks.length - 1; i >= 0; i--) {
|
|
55
|
+
const fn = shutdownHooks[i];
|
|
56
|
+
if (fn)
|
|
57
|
+
await Promise.resolve(fn());
|
|
58
|
+
}
|
|
59
|
+
await app.close();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
66
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
67
|
+
app._fastrackContext = ctx;
|
|
68
|
+
app._healthContributors = healthContributors;
|
|
69
|
+
return app;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the Fastrack context from an app (for actuator / other starters).
|
|
73
|
+
*/
|
|
74
|
+
export function getFastrackContext(app) {
|
|
75
|
+
return app._fastrackContext;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get health contributors map from app (for actuator).
|
|
79
|
+
*/
|
|
80
|
+
export function getHealthContributors(app) {
|
|
81
|
+
return app._healthContributors;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=create-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-app.js","sourceRoot":"","sources":["../src/create-app.ts"],"names":[],"mappings":"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"}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FastifyReply } from "fastify";
|
|
2
|
+
/**
|
|
3
|
+
* Problem-details error schema (design §5).
|
|
4
|
+
* Stack traces are logged server-side only; never exposed in response.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ProblemDetailsSchema: import("@sinclair/typebox").TObject<{
|
|
7
|
+
status: import("@sinclair/typebox").TNumber;
|
|
8
|
+
code: import("@sinclair/typebox").TString;
|
|
9
|
+
message: import("@sinclair/typebox").TString;
|
|
10
|
+
correlationId: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
11
|
+
}>;
|
|
12
|
+
export type ProblemDetails = typeof ProblemDetailsSchema.static;
|
|
13
|
+
/**
|
|
14
|
+
* Reply with problem-details body. Logs stack server-side only.
|
|
15
|
+
*/
|
|
16
|
+
export declare function replyError(reply: FastifyReply, err: unknown, options?: {
|
|
17
|
+
status?: number;
|
|
18
|
+
correlationId?: string;
|
|
19
|
+
}): void;
|
|
20
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,OAAO,oBAAoB,CAAC,MAAM,CAAC;AAEhE;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,OAAO,EACZ,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,IAAI,CAgBN"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
/**
|
|
3
|
+
* Problem-details error schema (design §5).
|
|
4
|
+
* Stack traces are logged server-side only; never exposed in response.
|
|
5
|
+
*/
|
|
6
|
+
export const ProblemDetailsSchema = Type.Object({
|
|
7
|
+
status: Type.Number({ description: "HTTP status" }),
|
|
8
|
+
code: Type.String({ description: "Error code" }),
|
|
9
|
+
message: Type.String({ description: "Human-readable message" }),
|
|
10
|
+
correlationId: Type.Optional(Type.String({ description: "Request correlation ID" })),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Reply with problem-details body. Logs stack server-side only.
|
|
14
|
+
*/
|
|
15
|
+
export function replyError(reply, err, options) {
|
|
16
|
+
const status = options?.status ?? err.statusCode ?? 500;
|
|
17
|
+
const correlationId = options?.correlationId ?? reply.request.correlationId;
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
const code = err.code ?? "INTERNAL_ERROR";
|
|
20
|
+
if (err instanceof Error && err.stack) {
|
|
21
|
+
reply.log?.error?.({ err, correlationId }, "Error (stack server-side only)");
|
|
22
|
+
}
|
|
23
|
+
reply.status(status).send({
|
|
24
|
+
status,
|
|
25
|
+
code,
|
|
26
|
+
message,
|
|
27
|
+
...(correlationId ? { correlationId } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;IACnD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;IAChD,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;IAC/D,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC,CAAC;CACrF,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,KAAmB,EACnB,GAAY,EACZ,OAAqD;IAErD,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAK,GAA+B,CAAC,UAAU,IAAI,GAAG,CAAC;IACrF,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAK,KAAK,CAAC,OAAsC,CAAC,aAAa,CAAC;IAC5G,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,IAAI,gBAAgB,CAAC;IAEjE,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACtC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,gCAAgC,CAAC,CAAC;IAC/E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;QACxB,MAAM;QACN,IAAI;QACJ,OAAO;QACP,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fastrack/core — Application bootstrap, starter loading, config, shutdown.
|
|
3
|
+
* @see https://github.com/fastrack-platform-js/fastrack
|
|
4
|
+
*/
|
|
5
|
+
export * from "./create-app.js";
|
|
6
|
+
export * from "./types.js";
|
|
7
|
+
export * from "./error.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fastrack/core — Application bootstrap, starter loading, config, shutdown.
|
|
3
|
+
* @see https://github.com/fastrack-platform-js/fastrack
|
|
4
|
+
*/
|
|
5
|
+
export * from "./create-app.js";
|
|
6
|
+
export * from "./types.js";
|
|
7
|
+
export * from "./error.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
/**
|
|
4
|
+
* Bootstrap context passed to starters.
|
|
5
|
+
*/
|
|
6
|
+
export interface FastrackContext {
|
|
7
|
+
/** Fastify app instance */
|
|
8
|
+
app: FastifyInstance;
|
|
9
|
+
/** Root config (env + file, merged with starter defaults) */
|
|
10
|
+
config: Record<string, unknown>;
|
|
11
|
+
/** Logger (optional; app.log if not set) */
|
|
12
|
+
logger?: {
|
|
13
|
+
info: (o: unknown) => void;
|
|
14
|
+
error: (o: unknown) => void;
|
|
15
|
+
child: (bindings: Record<string, unknown>) => unknown;
|
|
16
|
+
};
|
|
17
|
+
/** Readiness getter (core sets this) */
|
|
18
|
+
getReadiness?: () => boolean;
|
|
19
|
+
/** Set readiness (core sets this; actuator /ready uses it) */
|
|
20
|
+
setReadiness?: (ready: boolean) => void;
|
|
21
|
+
/** Register shutdown hook (called in reverse order on SIGTERM) */
|
|
22
|
+
onShutdown?: (fn: () => Promise<void> | void) => void;
|
|
23
|
+
/** Register health contributor (actuator uses this) */
|
|
24
|
+
registerHealthContributor?: (name: string, check: () => Promise<HealthResult>) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Health contributor result (design §9).
|
|
28
|
+
*/
|
|
29
|
+
export interface HealthResult {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
status?: "up" | "down";
|
|
32
|
+
details?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Health contributor interface (for actuator and starters that register).
|
|
36
|
+
*/
|
|
37
|
+
export interface HealthContributor {
|
|
38
|
+
name: string;
|
|
39
|
+
check(): Promise<HealthResult>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Starter interface (design §4).
|
|
43
|
+
* Each starter provides: TypeBox config schema, Fastify plugin registration, optional health/shutdown.
|
|
44
|
+
*/
|
|
45
|
+
export interface FastrackStarter<TConfig = unknown> {
|
|
46
|
+
name: string;
|
|
47
|
+
configSchema: TSchema;
|
|
48
|
+
defaults?: Partial<TConfig>;
|
|
49
|
+
register(app: FastifyInstance, ctx: FastrackContext, config: TConfig): Promise<void> | void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for createApp.
|
|
53
|
+
*/
|
|
54
|
+
export interface CreateAppOptions {
|
|
55
|
+
/** Starters to load (order is deterministic) */
|
|
56
|
+
starters?: FastrackStarter<unknown>[];
|
|
57
|
+
/** Override config (merged with env; starters get namespaced slice by starter name) */
|
|
58
|
+
config?: Record<string, unknown>;
|
|
59
|
+
/** Load .env file: path string (default ".env"), or false to disable. Uses dotenv. */
|
|
60
|
+
envPath?: string | false;
|
|
61
|
+
/** Shutdown timeout in ms (default 30_000) */
|
|
62
|
+
shutdownTimeoutMs?: number;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,GAAG,EAAE,eAAe,CAAC;IACrB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,4CAA4C;IAC5C,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KAAE,CAAC;IAC5H,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;IAC7B,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,kEAAkE;IAClE,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACtD,uDAAuD;IACvD,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;CACxF;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO,GAAG,OAAO;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7F;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,QAAQ,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACtC,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fastrack/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"dotenv": "^16.4.5",
|
|
16
|
+
"fastify": "^5.1.0",
|
|
17
|
+
"@sinclair/typebox": "^0.34.34"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.6.3"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
32
|
+
"lint": "echo ok",
|
|
33
|
+
"test": "echo ok"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { config as dotenvConfig } from "dotenv";
|
|
2
|
+
import { Value } from "@sinclair/typebox/value";
|
|
3
|
+
import Fastify from "fastify";
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { FastrackContext, CreateAppOptions } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_SHUTDOWN_MS = 30_000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a Fastrack app: load env (.env via dotenv), validate starter configs, register starters, set up shutdown.
|
|
11
|
+
*/
|
|
12
|
+
export async function createApp(options: CreateAppOptions = {}): Promise<FastifyInstance> {
|
|
13
|
+
const {
|
|
14
|
+
starters = [],
|
|
15
|
+
config: configOverride = {},
|
|
16
|
+
envPath = ".env",
|
|
17
|
+
shutdownTimeoutMs = DEFAULT_SHUTDOWN_MS,
|
|
18
|
+
} = options;
|
|
19
|
+
|
|
20
|
+
if (envPath !== false) {
|
|
21
|
+
dotenvConfig({ path: envPath });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const app = Fastify({ logger: true });
|
|
25
|
+
|
|
26
|
+
let readiness = true;
|
|
27
|
+
const shutdownHooks: Array<() => Promise<void> | void> = [];
|
|
28
|
+
const healthContributors: Map<string, () => Promise<{ ok: boolean; status?: "up" | "down"; details?: Record<string, unknown> }>> = new Map();
|
|
29
|
+
|
|
30
|
+
const ctx: FastrackContext = {
|
|
31
|
+
app,
|
|
32
|
+
config: { ...configOverride },
|
|
33
|
+
get logger() {
|
|
34
|
+
return app.log as FastrackContext["logger"];
|
|
35
|
+
},
|
|
36
|
+
getReadiness: () => readiness,
|
|
37
|
+
setReadiness: (ready: boolean) => {
|
|
38
|
+
readiness = ready;
|
|
39
|
+
},
|
|
40
|
+
onShutdown: (fn) => {
|
|
41
|
+
shutdownHooks.push(fn);
|
|
42
|
+
},
|
|
43
|
+
registerHealthContributor: (name: string, check: () => Promise<{ ok: boolean; status?: "up" | "down"; details?: Record<string, unknown> }>) => {
|
|
44
|
+
healthContributors.set(name, check);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (const starter of starters) {
|
|
49
|
+
const defaults = starter.defaults ?? {};
|
|
50
|
+
const raw = (ctx.config[starter.name] as Record<string, unknown>) ?? {};
|
|
51
|
+
const merged = { ...defaults, ...raw };
|
|
52
|
+
const errors = [...Value.Errors(starter.configSchema, merged)];
|
|
53
|
+
if (errors.length > 0) {
|
|
54
|
+
const msg = errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
55
|
+
throw new Error(`Config validation failed for starter "${starter.name}": ${msg}`);
|
|
56
|
+
}
|
|
57
|
+
const result = Value.Decode(starter.configSchema, merged) as unknown;
|
|
58
|
+
await Promise.resolve(starter.register(app, ctx, result));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const shutdown = async (signal: string) => {
|
|
62
|
+
readiness = false;
|
|
63
|
+
app.log?.info?.({ signal }, "Shutdown: readiness=false");
|
|
64
|
+
const timeout = setTimeout(() => {
|
|
65
|
+
app.log?.error?.("Shutdown timeout; exiting");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}, shutdownTimeoutMs);
|
|
68
|
+
try {
|
|
69
|
+
for (let i = shutdownHooks.length - 1; i >= 0; i--) {
|
|
70
|
+
const fn = shutdownHooks[i];
|
|
71
|
+
if (fn) await Promise.resolve(fn());
|
|
72
|
+
}
|
|
73
|
+
await app.close();
|
|
74
|
+
} finally {
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
80
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
81
|
+
|
|
82
|
+
(app as unknown as { _fastrackContext: FastrackContext })._fastrackContext = ctx;
|
|
83
|
+
(app as unknown as { _healthContributors: Map<string, () => Promise<{ ok: boolean; status?: "up" | "down"; details?: Record<string, unknown> }>> })._healthContributors = healthContributors;
|
|
84
|
+
|
|
85
|
+
return app;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the Fastrack context from an app (for actuator / other starters).
|
|
90
|
+
*/
|
|
91
|
+
export function getFastrackContext(app: FastifyInstance): FastrackContext | undefined {
|
|
92
|
+
return (app as FastifyInstance & { _fastrackContext?: FastrackContext })._fastrackContext;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get health contributors map from app (for actuator).
|
|
97
|
+
*/
|
|
98
|
+
export function getHealthContributors(
|
|
99
|
+
app: FastifyInstance
|
|
100
|
+
): Map<string, () => Promise<{ ok: boolean; status?: "up" | "down"; details?: Record<string, unknown> }>> | undefined {
|
|
101
|
+
return (app as unknown as { _healthContributors?: Map<string, () => Promise<{ ok: boolean; status?: "up" | "down"; details?: Record<string, unknown> }>> })._healthContributors;
|
|
102
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { FastifyReply } from "fastify";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Problem-details error schema (design §5).
|
|
6
|
+
* Stack traces are logged server-side only; never exposed in response.
|
|
7
|
+
*/
|
|
8
|
+
export const ProblemDetailsSchema = Type.Object({
|
|
9
|
+
status: Type.Number({ description: "HTTP status" }),
|
|
10
|
+
code: Type.String({ description: "Error code" }),
|
|
11
|
+
message: Type.String({ description: "Human-readable message" }),
|
|
12
|
+
correlationId: Type.Optional(Type.String({ description: "Request correlation ID" })),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type ProblemDetails = typeof ProblemDetailsSchema.static;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reply with problem-details body. Logs stack server-side only.
|
|
19
|
+
*/
|
|
20
|
+
export function replyError(
|
|
21
|
+
reply: FastifyReply,
|
|
22
|
+
err: unknown,
|
|
23
|
+
options?: { status?: number; correlationId?: string }
|
|
24
|
+
): void {
|
|
25
|
+
const status = options?.status ?? (err as { statusCode?: number }).statusCode ?? 500;
|
|
26
|
+
const correlationId = options?.correlationId ?? (reply.request as { correlationId?: string }).correlationId;
|
|
27
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28
|
+
const code = (err as { code?: string }).code ?? "INTERNAL_ERROR";
|
|
29
|
+
|
|
30
|
+
if (err instanceof Error && err.stack) {
|
|
31
|
+
reply.log?.error?.({ err, correlationId }, "Error (stack server-side only)");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reply.status(status).send({
|
|
35
|
+
status,
|
|
36
|
+
code,
|
|
37
|
+
message,
|
|
38
|
+
...(correlationId ? { correlationId } : {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap context passed to starters.
|
|
6
|
+
*/
|
|
7
|
+
export interface FastrackContext {
|
|
8
|
+
/** Fastify app instance */
|
|
9
|
+
app: FastifyInstance;
|
|
10
|
+
/** Root config (env + file, merged with starter defaults) */
|
|
11
|
+
config: Record<string, unknown>;
|
|
12
|
+
/** Logger (optional; app.log if not set) */
|
|
13
|
+
logger?: { info: (o: unknown) => void; error: (o: unknown) => void; child: (bindings: Record<string, unknown>) => unknown };
|
|
14
|
+
/** Readiness getter (core sets this) */
|
|
15
|
+
getReadiness?: () => boolean;
|
|
16
|
+
/** Set readiness (core sets this; actuator /ready uses it) */
|
|
17
|
+
setReadiness?: (ready: boolean) => void;
|
|
18
|
+
/** Register shutdown hook (called in reverse order on SIGTERM) */
|
|
19
|
+
onShutdown?: (fn: () => Promise<void> | void) => void;
|
|
20
|
+
/** Register health contributor (actuator uses this) */
|
|
21
|
+
registerHealthContributor?: (name: string, check: () => Promise<HealthResult>) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Health contributor result (design §9).
|
|
26
|
+
*/
|
|
27
|
+
export interface HealthResult {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
status?: "up" | "down";
|
|
30
|
+
details?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Health contributor interface (for actuator and starters that register).
|
|
35
|
+
*/
|
|
36
|
+
export interface HealthContributor {
|
|
37
|
+
name: string;
|
|
38
|
+
check(): Promise<HealthResult>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Starter interface (design §4).
|
|
43
|
+
* Each starter provides: TypeBox config schema, Fastify plugin registration, optional health/shutdown.
|
|
44
|
+
*/
|
|
45
|
+
export interface FastrackStarter<TConfig = unknown> {
|
|
46
|
+
name: string;
|
|
47
|
+
configSchema: TSchema;
|
|
48
|
+
defaults?: Partial<TConfig>;
|
|
49
|
+
register(app: FastifyInstance, ctx: FastrackContext, config: TConfig): Promise<void> | void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Options for createApp.
|
|
54
|
+
*/
|
|
55
|
+
export interface CreateAppOptions {
|
|
56
|
+
/** Starters to load (order is deterministic) */
|
|
57
|
+
starters?: FastrackStarter<unknown>[];
|
|
58
|
+
/** Override config (merged with env; starters get namespaced slice by starter name) */
|
|
59
|
+
config?: Record<string, unknown>;
|
|
60
|
+
/** Load .env file: path string (default ".env"), or false to disable. Uses dotenv. */
|
|
61
|
+
envPath?: string | false;
|
|
62
|
+
/** Shutdown timeout in ms (default 30_000) */
|
|
63
|
+
shutdownTimeoutMs?: number;
|
|
64
|
+
}
|