opinionated-machine 5.1.0 → 5.2.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.
Files changed (55) hide show
  1. package/README.md +1237 -278
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.js +5 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/AbstractController.d.ts +3 -3
  6. package/dist/lib/AbstractController.js.map +1 -1
  7. package/dist/lib/AbstractModule.d.ts +14 -0
  8. package/dist/lib/AbstractModule.js +16 -0
  9. package/dist/lib/AbstractModule.js.map +1 -1
  10. package/dist/lib/DIContext.d.ts +35 -0
  11. package/dist/lib/DIContext.js +99 -0
  12. package/dist/lib/DIContext.js.map +1 -1
  13. package/dist/lib/resolverFunctions.d.ts +33 -0
  14. package/dist/lib/resolverFunctions.js +46 -0
  15. package/dist/lib/resolverFunctions.js.map +1 -1
  16. package/dist/lib/sse/AbstractSSEController.d.ts +163 -0
  17. package/dist/lib/sse/AbstractSSEController.js +228 -0
  18. package/dist/lib/sse/AbstractSSEController.js.map +1 -0
  19. package/dist/lib/sse/SSEConnectionSpy.d.ts +55 -0
  20. package/dist/lib/sse/SSEConnectionSpy.js +136 -0
  21. package/dist/lib/sse/SSEConnectionSpy.js.map +1 -0
  22. package/dist/lib/sse/index.d.ts +5 -0
  23. package/dist/lib/sse/index.js +6 -0
  24. package/dist/lib/sse/index.js.map +1 -0
  25. package/dist/lib/sse/sseContracts.d.ts +132 -0
  26. package/dist/lib/sse/sseContracts.js +102 -0
  27. package/dist/lib/sse/sseContracts.js.map +1 -0
  28. package/dist/lib/sse/sseParser.d.ts +167 -0
  29. package/dist/lib/sse/sseParser.js +225 -0
  30. package/dist/lib/sse/sseParser.js.map +1 -0
  31. package/dist/lib/sse/sseRouteBuilder.d.ts +47 -0
  32. package/dist/lib/sse/sseRouteBuilder.js +114 -0
  33. package/dist/lib/sse/sseRouteBuilder.js.map +1 -0
  34. package/dist/lib/sse/sseTypes.d.ts +164 -0
  35. package/dist/lib/sse/sseTypes.js +2 -0
  36. package/dist/lib/sse/sseTypes.js.map +1 -0
  37. package/dist/lib/testing/index.d.ts +5 -0
  38. package/dist/lib/testing/index.js +5 -0
  39. package/dist/lib/testing/index.js.map +1 -0
  40. package/dist/lib/testing/sseHttpClient.d.ts +203 -0
  41. package/dist/lib/testing/sseHttpClient.js +262 -0
  42. package/dist/lib/testing/sseHttpClient.js.map +1 -0
  43. package/dist/lib/testing/sseInjectClient.d.ts +173 -0
  44. package/dist/lib/testing/sseInjectClient.js +234 -0
  45. package/dist/lib/testing/sseInjectClient.js.map +1 -0
  46. package/dist/lib/testing/sseInjectHelpers.d.ts +59 -0
  47. package/dist/lib/testing/sseInjectHelpers.js +117 -0
  48. package/dist/lib/testing/sseInjectHelpers.js.map +1 -0
  49. package/dist/lib/testing/sseTestServer.d.ts +93 -0
  50. package/dist/lib/testing/sseTestServer.js +108 -0
  51. package/dist/lib/testing/sseTestServer.js.map +1 -0
  52. package/dist/lib/testing/sseTestTypes.d.ts +106 -0
  53. package/dist/lib/testing/sseTestTypes.js +2 -0
  54. package/dist/lib/testing/sseTestTypes.js.map +1 -0
  55. package/package.json +80 -78
@@ -0,0 +1,108 @@
1
+ import FastifySSEPlugin from '@fastify/sse';
2
+ import fastify, {} from 'fastify';
3
+ /**
4
+ * Test server for SSE e2e testing with automatic setup and cleanup.
5
+ *
6
+ * This class simplifies SSE e2e test setup by:
7
+ * - Creating a Fastify instance with @fastify/sse plugin pre-registered
8
+ * - Starting a real HTTP server on a random port
9
+ * - Providing a base URL for making HTTP requests
10
+ * - Handling cleanup on close()
11
+ *
12
+ * **When to use SSETestServer:**
13
+ * - Testing with `SSEHttpClient` (requires real HTTP server)
14
+ * - E2E tests that need to verify actual network behavior
15
+ * - Tests that need to run controller code in a real server context
16
+ *
17
+ * **Note:** For simple tests using `SSEInjectClient`, you don't need this class -
18
+ * you can use the Fastify instance directly without starting a server.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Basic usage
23
+ * const server = await SSETestServer.create(async (app) => {
24
+ * // Register your SSE routes
25
+ * app.get('/api/events', async (request, reply) => {
26
+ * reply.sse({ event: 'message', data: { hello: 'world' } })
27
+ * reply.sseClose()
28
+ * })
29
+ * })
30
+ *
31
+ * // Connect using SSEHttpClient
32
+ * const client = await SSEHttpClient.connect(server.baseUrl, '/api/events')
33
+ * const events = await client.collectEvents(1)
34
+ * expect(events[0].event).toBe('message')
35
+ *
36
+ * // Cleanup
37
+ * client.close()
38
+ * await server.close()
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // With custom resources (e.g., DI container, controllers)
44
+ * const server = await SSETestServer.create(
45
+ * async (app) => {
46
+ * // Routes can access resources via closure
47
+ * myController.registerRoutes(app)
48
+ * },
49
+ * {
50
+ * configureApp: async (app) => {
51
+ * // Configure validators, plugins, etc.
52
+ * app.setValidatorCompiler(validatorCompiler)
53
+ * },
54
+ * setup: async () => {
55
+ * // Create resources that will be available via server.resources
56
+ * const container = createContainer()
57
+ * const controller = container.resolve('sseController')
58
+ * return { container, controller }
59
+ * },
60
+ * }
61
+ * )
62
+ *
63
+ * // Access resources to interact with the server
64
+ * const { controller } = server.resources
65
+ * controller.broadcastEvent({ event: 'update', data: { value: 42 } })
66
+ *
67
+ * await server.close()
68
+ * ```
69
+ */
70
+ export class SSETestServer {
71
+ /** The Fastify instance */
72
+ app;
73
+ /** Base URL for the running server (e.g., "http://localhost:3000") */
74
+ baseUrl;
75
+ /** Custom resources from setup function */
76
+ resources;
77
+ constructor(app, baseUrl, resources) {
78
+ this.app = app;
79
+ this.baseUrl = baseUrl;
80
+ this.resources = resources;
81
+ }
82
+ static async create(registerRoutes, options) {
83
+ // Create Fastify app
84
+ const app = fastify();
85
+ // Register SSE plugin (type assertion needed due to @fastify/sse's module.exports pattern)
86
+ await app.register(FastifySSEPlugin);
87
+ // Run custom configuration
88
+ if (options?.configureApp) {
89
+ await options.configureApp(app);
90
+ }
91
+ // Setup custom resources
92
+ const resources = options?.setup ? await options.setup() : undefined;
93
+ // Register routes
94
+ await registerRoutes(app);
95
+ // Start the server on random port
96
+ await app.listen({ port: 0 });
97
+ const address = app.server.address();
98
+ const baseUrl = typeof address === 'string' ? address : `http://localhost:${address?.port}`;
99
+ return new SSETestServer(app, baseUrl, resources);
100
+ }
101
+ /**
102
+ * Close the server and cleanup resources.
103
+ */
104
+ async close() {
105
+ await this.app.close();
106
+ }
107
+ }
108
+ //# sourceMappingURL=sseTestServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sseTestServer.js","sourceRoot":"","sources":["../../../lib/testing/sseTestServer.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,cAAc,CAAA;AAC3C,OAAO,OAAO,EAAE,EAAwB,MAAM,SAAS,CAAA;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,MAAM,OAAO,aAAa;IACxB,2BAA2B;IAClB,GAAG,CAAiB;IAC7B,sEAAsE;IAC7D,OAAO,CAAQ;IACxB,2CAA2C;IAClC,SAAS,CAAG;IAErB,YAAoB,GAAoB,EAAE,OAAe,EAAE,SAAY;QACrE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;IAC5B,CAAC;IAkBD,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,cAA8D,EAC9D,OAAuC;QAEvC,qBAAqB;QACrB,MAAM,GAAG,GAAG,OAAO,EAAE,CAAA;QAErB,2FAA2F;QAC3F,MAAM,GAAG,CAAC,QAAQ,CAAC,gBAAiE,CAAC,CAAA;QAErF,2BAA2B;QAC3B,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;YAC1B,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC;QAED,yBAAyB;QACzB,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAE,SAAe,CAAA;QAE3E,kBAAkB;QAClB,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;QAEzB,kCAAkC;QAClC,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;QAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,OAAO,EAAE,IAAI,EAAE,CAAA;QAE3F,OAAO,IAAI,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;IACnD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;IACxB,CAAC;CACF"}
@@ -0,0 +1,106 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { z } from 'zod';
3
+ import type { AnySSERouteDefinition } from '../sse/sseContracts.ts';
4
+ import type { ParsedSSEEvent } from '../sse/sseParser.ts';
5
+ /**
6
+ * Represents an active SSE test connection (inject-based).
7
+ *
8
+ * This interface is used with Fastify's inject() for testing SSE endpoints
9
+ * synchronously. For long-lived connections, use SSEHttpClient instead.
10
+ */
11
+ export interface SSETestConnection {
12
+ /**
13
+ * Wait for a specific event by name.
14
+ * @param eventName - The event name to wait for
15
+ * @param timeout - Timeout in milliseconds (default: 5000)
16
+ */
17
+ waitForEvent(eventName: string, timeout?: number): Promise<ParsedSSEEvent>;
18
+ /**
19
+ * Wait for a specific number of events.
20
+ * @param count - Number of events to wait for
21
+ * @param timeout - Timeout in milliseconds (default: 5000)
22
+ */
23
+ waitForEvents(count: number, timeout?: number): Promise<ParsedSSEEvent[]>;
24
+ /**
25
+ * Get all events received so far.
26
+ */
27
+ getReceivedEvents(): ParsedSSEEvent[];
28
+ /**
29
+ * Close the connection.
30
+ */
31
+ close(): void;
32
+ /**
33
+ * Check if connection is closed.
34
+ */
35
+ isClosed(): boolean;
36
+ /**
37
+ * Get the HTTP response status code.
38
+ */
39
+ getStatusCode(): number;
40
+ /**
41
+ * Get response headers.
42
+ */
43
+ getHeaders(): Record<string, string | string[] | undefined>;
44
+ }
45
+ /**
46
+ * Options for establishing an SSE connection.
47
+ */
48
+ export type SSEConnectOptions = {
49
+ headers?: Record<string, string>;
50
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
51
+ body?: unknown;
52
+ };
53
+ /**
54
+ * Options for injectSSE (GET SSE routes).
55
+ */
56
+ export type InjectSSEOptions<Contract extends AnySSERouteDefinition> = {
57
+ params?: z.infer<Contract['params']>;
58
+ query?: z.infer<Contract['query']>;
59
+ headers?: z.infer<Contract['requestHeaders']>;
60
+ };
61
+ /**
62
+ * Options for injectPayloadSSE (POST/PUT/PATCH SSE routes).
63
+ */
64
+ export type InjectPayloadSSEOptions<Contract extends AnySSERouteDefinition> = {
65
+ params?: z.infer<Contract['params']>;
66
+ query?: z.infer<Contract['query']>;
67
+ headers?: z.infer<Contract['requestHeaders']>;
68
+ body: Contract['body'] extends z.ZodTypeAny ? z.infer<Contract['body']> : never;
69
+ };
70
+ /**
71
+ * SSE response data.
72
+ */
73
+ export type SSEResponse = {
74
+ statusCode: number;
75
+ headers: Record<string, string | string[] | undefined>;
76
+ body: string;
77
+ };
78
+ /**
79
+ * Result of an SSE inject call.
80
+ *
81
+ * Note: Fastify's inject() waits for the full response, so these helpers
82
+ * work best for streaming that completes (OpenAI-style). For long-lived
83
+ * SSE connections, use `SSEHttpClient` with a real HTTP server instead.
84
+ */
85
+ export type InjectSSEResult = {
86
+ /**
87
+ * Resolves when the response completes with the full SSE body.
88
+ * Parse the body with `parseSSEEvents()` to get individual events.
89
+ */
90
+ closed: Promise<SSEResponse>;
91
+ };
92
+ /**
93
+ * Options for creating an SSE test server.
94
+ */
95
+ export type CreateSSETestServerOptions<T> = {
96
+ /**
97
+ * Configure the Fastify instance before SSE routes are registered.
98
+ * Use this to add plugins, validators, etc.
99
+ */
100
+ configureApp?: (app: FastifyInstance<any, any, any, any>) => void | Promise<void>;
101
+ /**
102
+ * Custom setup function that returns resources to be cleaned up.
103
+ * The returned value will be passed to the cleanup function.
104
+ */
105
+ setup?: () => T | Promise<T>;
106
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sseTestTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sseTestTypes.js","sourceRoot":"","sources":["../../../lib/testing/sseTestTypes.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,78 +1,80 @@
1
- {
2
- "name": "opinionated-machine",
3
- "version": "5.1.0",
4
- "description": "Very opinionated DI framework for fastify, built on top of awilix ",
5
- "type": "module",
6
- "license": "MIT",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": "./dist/index.js",
11
- "./package.json": "./package.json"
12
- },
13
- "maintainers": [
14
- {
15
- "name": "Igor Savin",
16
- "email": "kibertoad@gmail.com"
17
- }
18
- ],
19
- "scripts": {
20
- "build": "rimraf dist && tsc -p tsconfig.build.json",
21
- "lint": "biome check . && tsc",
22
- "lint:fix": "biome check --write .",
23
- "test": "vitest --typecheck",
24
- "test:ci": "npm run test -- --coverage",
25
- "prepublishOnly": "npm run build"
26
- },
27
- "repository": {
28
- "type": "git",
29
- "url": "git+https://github.com/kibertoad/opinionated-machine.git"
30
- },
31
- "dependencies": {
32
- "ts-deepmerge": "^7.0.3"
33
- },
34
- "peerDependencies": {
35
- "@lokalise/fastify-api-contracts": ">=5.0.0",
36
- "@lokalise/api-contracts": ">=6.0.0",
37
- "awilix": ">=12.0.0",
38
- "awilix-manager": ">=6.0.0",
39
- "fastify": ">=5.0.0",
40
- "fastify-type-provider-zod": ">=6.1.0",
41
- "zod": ">=4.1.12"
42
- },
43
- "devDependencies": {
44
- "@types/node": "^22.19.1",
45
- "@biomejs/biome": "2.3.8",
46
- "@lokalise/biome-config": "^3.1.0",
47
- "@lokalise/fastify-api-contracts": "^5.0.0",
48
- "@lokalise/tsconfig": "^3.0.0",
49
- "@lokalise/api-contracts": "^6.1.0",
50
- "@vitest/coverage-v8": "^4.0.15",
51
- "awilix": "^12.0.5",
52
- "awilix-manager": "^6.1.0",
53
- "fastify": "^5.6.2",
54
- "fastify-type-provider-zod": "^6.1.0",
55
- "vitest": "^4.0.15",
56
- "rimraf": "^6.1.0",
57
- "typescript": "^5.9.3",
58
- "zod": "^4.1.13"
59
- },
60
- "private": false,
61
- "publishConfig": {
62
- "access": "public"
63
- },
64
- "keywords": [
65
- "dependency",
66
- "injection",
67
- "opinionated",
68
- "awilix",
69
- "di",
70
- "fastify"
71
- ],
72
- "homepage": "https://github.com/kibertoad/opinionated-machine",
73
- "files": [
74
- "README.md",
75
- "LICENSE",
76
- "dist/*"
77
- ]
78
- }
1
+ {
2
+ "name": "opinionated-machine",
3
+ "version": "5.2.0",
4
+ "description": "Very opinionated DI framework for fastify, built on top of awilix ",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": "./dist/index.js",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "maintainers": [
14
+ {
15
+ "name": "Igor Savin",
16
+ "email": "kibertoad@gmail.com"
17
+ }
18
+ ],
19
+ "scripts": {
20
+ "build": "rimraf dist && tsc -p tsconfig.build.json",
21
+ "lint": "biome check . && tsc",
22
+ "lint:fix": "biome check --write .",
23
+ "test": "vitest --typecheck",
24
+ "test:ci": "npm run test -- --coverage",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/kibertoad/opinionated-machine.git"
30
+ },
31
+ "dependencies": {
32
+ "@fastify/sse": "^0.4.0",
33
+ "fast-querystring": "^1.1.2",
34
+ "ts-deepmerge": "^7.0.3"
35
+ },
36
+ "peerDependencies": {
37
+ "@lokalise/fastify-api-contracts": ">=5.0.0",
38
+ "@lokalise/api-contracts": ">=6.0.0",
39
+ "awilix": ">=12.0.0",
40
+ "awilix-manager": ">=6.0.0",
41
+ "fastify": ">=5.0.0",
42
+ "fastify-type-provider-zod": ">=6.1.0",
43
+ "zod": ">=4.1.12"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.19.7",
47
+ "@biomejs/biome": "2.3.11",
48
+ "@lokalise/biome-config": "^3.1.1",
49
+ "@lokalise/fastify-api-contracts": "^5.0.0",
50
+ "@lokalise/tsconfig": "^3.1.0",
51
+ "@lokalise/api-contracts": "^6.1.0",
52
+ "@vitest/coverage-v8": "^4.0.17",
53
+ "awilix": "^12.0.5",
54
+ "awilix-manager": "^6.3.0",
55
+ "fastify": "^5.7.0",
56
+ "fastify-type-provider-zod": "^6.1.0",
57
+ "vitest": "^4.0.17",
58
+ "rimraf": "^6.1.2",
59
+ "typescript": "^5.9.3",
60
+ "zod": "^4.3.5"
61
+ },
62
+ "private": false,
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "keywords": [
67
+ "dependency",
68
+ "injection",
69
+ "opinionated",
70
+ "awilix",
71
+ "di",
72
+ "fastify"
73
+ ],
74
+ "homepage": "https://github.com/kibertoad/opinionated-machine",
75
+ "files": [
76
+ "README.md",
77
+ "LICENSE",
78
+ "dist/*"
79
+ ]
80
+ }