@magek/server 0.0.6 → 0.0.8
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/dist/index.js +6 -11
- package/dist/infrastructure/controllers/graphql.js +1 -0
- package/dist/infrastructure/controllers/health-controller.js +1 -0
- package/dist/infrastructure/event-poller.d.ts +18 -0
- package/dist/infrastructure/event-poller.js +82 -0
- package/dist/infrastructure/test-helper/local-test-helper.js +2 -0
- package/dist/infrastructure/websocket-registry.js +1 -3
- package/dist/internal-info.d.ts +2 -0
- package/dist/internal-info.js +6 -0
- package/dist/library/graphql-adapter.js +5 -5
- package/dist/library/health-adapter.js +4 -3
- package/dist/server.js +6 -0
- package/dist/services/graphql-service.js +1 -0
- package/dist/services/health-service.js +1 -0
- package/package.json +16 -15
package/dist/index.js
CHANGED
|
@@ -46,42 +46,37 @@ exports.ServerRuntime = {
|
|
|
46
46
|
},
|
|
47
47
|
sensor: {
|
|
48
48
|
databaseEventsHealthDetails: (config) => {
|
|
49
|
-
var _a;
|
|
50
49
|
// Delegate to event store adapter health check if available
|
|
51
|
-
if (
|
|
50
|
+
if (config.eventStoreAdapter?.healthCheck) {
|
|
52
51
|
return config.eventStoreAdapter.healthCheck.details(config);
|
|
53
52
|
}
|
|
54
53
|
throw new Error('No event store adapter configured for health checks');
|
|
55
54
|
},
|
|
56
55
|
databaseReadModelsHealthDetails: (config) => {
|
|
57
|
-
var _a;
|
|
58
56
|
// Delegate to read model store adapter health check if available
|
|
59
|
-
if (
|
|
57
|
+
if (config.readModelStoreAdapter?.healthCheck) {
|
|
60
58
|
return config.readModelStoreAdapter.healthCheck.details(config);
|
|
61
59
|
}
|
|
62
60
|
throw new Error('No read model store adapter configured for health checks');
|
|
63
61
|
},
|
|
64
62
|
isDatabaseEventUp: (config) => {
|
|
65
|
-
var _a;
|
|
66
63
|
// Delegate to event store adapter health check if available
|
|
67
|
-
if (
|
|
64
|
+
if (config.eventStoreAdapter?.healthCheck) {
|
|
68
65
|
return config.eventStoreAdapter.healthCheck.isUp(config);
|
|
69
66
|
}
|
|
70
67
|
return Promise.resolve(false);
|
|
71
68
|
},
|
|
72
69
|
areDatabaseReadModelsUp: (config) => {
|
|
73
|
-
var _a;
|
|
74
70
|
// Delegate to read model store adapter health check if available
|
|
75
|
-
if (
|
|
71
|
+
if (config.readModelStoreAdapter?.healthCheck) {
|
|
76
72
|
return config.readModelStoreAdapter.healthCheck.isUp(config);
|
|
77
73
|
}
|
|
78
74
|
return Promise.resolve(false);
|
|
79
75
|
},
|
|
80
76
|
databaseUrls: (config) => {
|
|
81
|
-
var _a, _b, _c, _d, _e, _f;
|
|
82
77
|
// Get URLs from both event store and read model store adapters
|
|
83
|
-
const eventUrls =
|
|
84
|
-
const readModelUrls =
|
|
78
|
+
const eventUrls = config.eventStoreAdapter?.healthCheck?.urls(config) ?? Promise.resolve([]);
|
|
79
|
+
const readModelUrls = config.readModelStoreAdapter?.healthCheck?.urls(config) ?? Promise.resolve([]);
|
|
85
80
|
return Promise.all([eventUrls, readModelUrls]).then(([events, readModels]) => [
|
|
86
81
|
...events,
|
|
87
82
|
...readModels
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { UserApp, MagekConfig } from '@magek/common';
|
|
2
|
+
/**
|
|
3
|
+
* Starts the event polling loop.
|
|
4
|
+
* Polls for unprocessed events and dispatches them via eventDispatcher.
|
|
5
|
+
*
|
|
6
|
+
* @param userApp - The user's Magek application module
|
|
7
|
+
* @param config - The Magek configuration object
|
|
8
|
+
*/
|
|
9
|
+
export declare function startEventPolling(userApp: UserApp, config: MagekConfig): void;
|
|
10
|
+
/**
|
|
11
|
+
* Waits for the current poll cycle to complete.
|
|
12
|
+
* Exported for tests to ensure async poll callbacks finish before assertions.
|
|
13
|
+
*/
|
|
14
|
+
export declare function waitForCurrentPoll(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Stops the event polling loop.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stopEventPolling(): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startEventPolling = startEventPolling;
|
|
4
|
+
exports.waitForCurrentPoll = waitForCurrentPoll;
|
|
5
|
+
exports.stopEventPolling = stopEventPolling;
|
|
6
|
+
const common_1 = require("@magek/common");
|
|
7
|
+
let pollingTimer = null;
|
|
8
|
+
let currentPollPromise = null;
|
|
9
|
+
/**
|
|
10
|
+
* Starts the event polling loop.
|
|
11
|
+
* Polls for unprocessed events and dispatches them via eventDispatcher.
|
|
12
|
+
*
|
|
13
|
+
* @param userApp - The user's Magek application module
|
|
14
|
+
* @param config - The Magek configuration object
|
|
15
|
+
*/
|
|
16
|
+
function startEventPolling(userApp, config) {
|
|
17
|
+
const logger = (0, common_1.getLogger)(config, 'EventPoller');
|
|
18
|
+
const intervalMs = config.eventPollingIntervalMs;
|
|
19
|
+
logger.info(`Starting event polling every ${intervalMs}ms (batch size: ${config.eventProcessingBatchSize})`);
|
|
20
|
+
pollingTimer = setInterval(() => {
|
|
21
|
+
// Skip if previous poll is still running to prevent overlapping executions
|
|
22
|
+
if (currentPollPromise) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Store the promise so tests can await it and we can prevent overlap
|
|
26
|
+
currentPollPromise = pollAndProcessEvents(userApp, config, logger).finally(() => {
|
|
27
|
+
currentPollPromise = null;
|
|
28
|
+
});
|
|
29
|
+
}, intervalMs);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Waits for the current poll cycle to complete.
|
|
33
|
+
* Exported for tests to ensure async poll callbacks finish before assertions.
|
|
34
|
+
*/
|
|
35
|
+
async function waitForCurrentPoll() {
|
|
36
|
+
if (currentPollPromise) {
|
|
37
|
+
await currentPollPromise;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Stops the event polling loop.
|
|
42
|
+
*/
|
|
43
|
+
function stopEventPolling() {
|
|
44
|
+
if (pollingTimer) {
|
|
45
|
+
clearInterval(pollingTimer);
|
|
46
|
+
pollingTimer = null;
|
|
47
|
+
}
|
|
48
|
+
currentPollPromise = null;
|
|
49
|
+
}
|
|
50
|
+
async function pollAndProcessEvents(userApp, config, logger) {
|
|
51
|
+
const eventStore = config.eventStore;
|
|
52
|
+
// Check if adapter supports async processing
|
|
53
|
+
if (!eventStore.fetchUnprocessedEvents || !eventStore.markEventProcessed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const events = await eventStore.fetchUnprocessedEvents(config);
|
|
58
|
+
if (events.length === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
logger.debug(`Processing ${events.length} events`);
|
|
62
|
+
// Process each event individually
|
|
63
|
+
for (const event of events) {
|
|
64
|
+
try {
|
|
65
|
+
// Dispatch the event
|
|
66
|
+
await userApp.eventDispatcher([event]);
|
|
67
|
+
// Mark as processed only after successful dispatch
|
|
68
|
+
if (event.id) {
|
|
69
|
+
await eventStore.markEventProcessed(config, event.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger.error(`Error processing event ${event.id}:`, error);
|
|
74
|
+
// Stop processing on first error to maintain order
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
logger.error('Error during event polling:', error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LocalTestHelper = void 0;
|
|
4
4
|
const local_queries_1 = require("./local-queries");
|
|
5
5
|
class LocalTestHelper {
|
|
6
|
+
outputs;
|
|
7
|
+
queries;
|
|
6
8
|
constructor(outputs, queries) {
|
|
7
9
|
this.outputs = outputs;
|
|
8
10
|
this.queries = queries;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.localPort = exports.MAGEK_LOCAL_PORT = void 0;
|
|
4
|
+
exports.MAGEK_LOCAL_PORT = 'INTERNAL_LOCAL_PORT';
|
|
5
|
+
const localPort = () => process.env[exports.MAGEK_LOCAL_PORT] || '3000';
|
|
6
|
+
exports.localPort = localPort;
|
|
@@ -13,16 +13,16 @@ function webSocketMessageToEnvelope(config, webSocketRequest, requestID) {
|
|
|
13
13
|
logger.debug('Received WebSocket GraphQL request: ', webSocketRequest);
|
|
14
14
|
let eventType = 'MESSAGE';
|
|
15
15
|
const incomingMessage = webSocketRequest.incomingMessage;
|
|
16
|
-
const headers = incomingMessage
|
|
16
|
+
const headers = incomingMessage?.headers;
|
|
17
17
|
const data = webSocketRequest.data;
|
|
18
18
|
try {
|
|
19
19
|
const connectionContext = webSocketRequest.connectionContext;
|
|
20
|
-
eventType = connectionContext
|
|
20
|
+
eventType = connectionContext?.eventType;
|
|
21
21
|
return {
|
|
22
22
|
requestID,
|
|
23
23
|
eventType,
|
|
24
|
-
connectionID: connectionContext
|
|
25
|
-
token: Array.isArray(headers
|
|
24
|
+
connectionID: connectionContext?.connectionId.toString(),
|
|
25
|
+
token: Array.isArray(headers?.authorization) ? headers.authorization[0] : headers?.authorization,
|
|
26
26
|
value: data,
|
|
27
27
|
context: {
|
|
28
28
|
request: {
|
|
@@ -60,7 +60,7 @@ function httpMessageToEnvelope(config, httpRequest, requestId) {
|
|
|
60
60
|
connectionID: undefined,
|
|
61
61
|
requestID: requestId,
|
|
62
62
|
eventType: eventType,
|
|
63
|
-
token: Array.isArray(headers
|
|
63
|
+
token: Array.isArray(headers?.authorization) ? headers.authorization[0] : headers?.authorization,
|
|
64
64
|
value: data,
|
|
65
65
|
context: {
|
|
66
66
|
request: {
|
|
@@ -8,6 +8,7 @@ exports.isGraphQLFunctionUp = isGraphQLFunctionUp;
|
|
|
8
8
|
exports.rawRequestToSensorHealth = rawRequestToSensorHealth;
|
|
9
9
|
const paths_1 = require("../paths");
|
|
10
10
|
const common_1 = require("@magek/common");
|
|
11
|
+
const internal_info_1 = require("../internal-info");
|
|
11
12
|
const fs_1 = require("fs");
|
|
12
13
|
async function databaseUrl() {
|
|
13
14
|
return [paths_1.eventsDatabase, paths_1.readModelsDatabase];
|
|
@@ -15,11 +16,11 @@ async function databaseUrl() {
|
|
|
15
16
|
async function countAll(database) {
|
|
16
17
|
await database.loadDatabaseAsync();
|
|
17
18
|
const count = await database.countAsync({});
|
|
18
|
-
return count
|
|
19
|
+
return count ?? 0;
|
|
19
20
|
}
|
|
20
21
|
async function graphqlFunctionUrl() {
|
|
21
22
|
try {
|
|
22
|
-
const port = (0,
|
|
23
|
+
const port = (0, internal_info_1.localPort)();
|
|
23
24
|
return `http://localhost:${port}/graphql`;
|
|
24
25
|
}
|
|
25
26
|
catch (e) {
|
|
@@ -65,6 +66,6 @@ function rawRequestToSensorHealth(rawRequest) {
|
|
|
65
66
|
rawContext: rawRequest,
|
|
66
67
|
},
|
|
67
68
|
componentPath: componentPath,
|
|
68
|
-
token: Array.isArray(headers
|
|
69
|
+
token: Array.isArray(headers?.authorization) ? headers.authorization[0] : headers?.authorization,
|
|
69
70
|
};
|
|
70
71
|
}
|
package/dist/server.js
CHANGED
|
@@ -13,6 +13,7 @@ const health_controller_1 = require("./infrastructure/controllers/health-control
|
|
|
13
13
|
const websocket_registry_1 = require("./infrastructure/websocket-registry");
|
|
14
14
|
const http_1 = require("./infrastructure/http");
|
|
15
15
|
const scheduler_1 = require("./infrastructure/scheduler");
|
|
16
|
+
const event_poller_1 = require("./infrastructure/event-poller");
|
|
16
17
|
// Global WebSocket registry instance
|
|
17
18
|
let globalWebSocketRegistry;
|
|
18
19
|
/**
|
|
@@ -191,6 +192,11 @@ async function createServer(userApp, options = {}) {
|
|
|
191
192
|
const config = userApp.Magek.config;
|
|
192
193
|
if (config) {
|
|
193
194
|
(0, scheduler_1.configureScheduler)(config, userApp);
|
|
195
|
+
(0, event_poller_1.startEventPolling)(userApp, config);
|
|
196
|
+
// Clean up event polling when server shuts down
|
|
197
|
+
fastify.addHook('onClose', async () => {
|
|
198
|
+
(0, event_poller_1.stopEventPolling)();
|
|
199
|
+
});
|
|
194
200
|
}
|
|
195
201
|
await fastify.ready();
|
|
196
202
|
return fastify;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magek/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Debug your Magek projects locally",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"server"
|
|
@@ -26,20 +26,30 @@
|
|
|
26
26
|
"node": ">=22.0.0 <23.0.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@magek/common": "
|
|
29
|
+
"@magek/common": "workspace:^0.0.8",
|
|
30
30
|
"@fastify/cors": "11.2.0",
|
|
31
31
|
"@fastify/websocket": "11.2.0",
|
|
32
32
|
"@seald-io/nedb": "4.1.2",
|
|
33
|
-
"fastify": "5.7.
|
|
34
|
-
"fastify-sse-v2": "4.2.
|
|
33
|
+
"fastify": "5.7.3",
|
|
34
|
+
"fastify-sse-v2": "4.2.2",
|
|
35
35
|
"node-schedule": "2.1.1",
|
|
36
36
|
"tslib": "2.8.1"
|
|
37
37
|
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"format": "prettier --write --ext '.js,.ts' **/*.ts **/*/*.ts",
|
|
40
|
+
"lint:check": "eslint \"**/*.ts\"",
|
|
41
|
+
"lint:fix": "eslint --quiet --fix \"**/*.ts\"",
|
|
42
|
+
"build": "tsc -b tsconfig.json",
|
|
43
|
+
"clean": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
44
|
+
"prepack": "tsc -b tsconfig.json",
|
|
45
|
+
"test:provider-local": "npm run test",
|
|
46
|
+
"test": "tsc --noEmit -p tsconfig.test.json && MAGEK_ENV=test c8 mocha --forbid-only \"test/**/*.test.ts\""
|
|
47
|
+
},
|
|
38
48
|
"bugs": {
|
|
39
49
|
"url": "https://github.com/theam/magek/issues"
|
|
40
50
|
},
|
|
41
51
|
"devDependencies": {
|
|
42
|
-
"@magek/eslint-config": "
|
|
52
|
+
"@magek/eslint-config": "workspace:^0.0.8",
|
|
43
53
|
"@types/chai": "5.2.3",
|
|
44
54
|
"@types/chai-as-promised": "8.0.2",
|
|
45
55
|
"@types/mocha": "10.0.10",
|
|
@@ -57,14 +67,5 @@
|
|
|
57
67
|
"sinon-chai": "4.0.1",
|
|
58
68
|
"tsx": "^4.19.2",
|
|
59
69
|
"typescript": "5.9.3"
|
|
60
|
-
},
|
|
61
|
-
"scripts": {
|
|
62
|
-
"format": "prettier --write --ext '.js,.ts' **/*.ts **/*/*.ts",
|
|
63
|
-
"lint:check": "eslint \"**/*.ts\"",
|
|
64
|
-
"lint:fix": "eslint --quiet --fix \"**/*.ts\"",
|
|
65
|
-
"build": "tsc -b tsconfig.json",
|
|
66
|
-
"clean": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
67
|
-
"test:provider-local": "npm run test",
|
|
68
|
-
"test": "tsc --noEmit -p tsconfig.test.json && MAGEK_ENV=test c8 mocha --forbid-only \"test/**/*.test.ts\""
|
|
69
70
|
}
|
|
70
|
-
}
|
|
71
|
+
}
|