@magek/adapter-session-store-nedb 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ export { NedbSessionStoreAdapter } from './nedb-session-store-adapter';
2
+ export { WebSocketRegistry } from './web-socket-registry';
3
+ export { connectionsDatabase, subscriptionsDatabase } from './paths';
4
+ import { NedbSessionStoreAdapter } from './nedb-session-store-adapter';
5
+ export declare const sessionStore: NedbSessionStoreAdapter;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionStore = exports.subscriptionsDatabase = exports.connectionsDatabase = exports.WebSocketRegistry = exports.NedbSessionStoreAdapter = void 0;
4
+ var nedb_session_store_adapter_1 = require("./nedb-session-store-adapter");
5
+ Object.defineProperty(exports, "NedbSessionStoreAdapter", { enumerable: true, get: function () { return nedb_session_store_adapter_1.NedbSessionStoreAdapter; } });
6
+ var web_socket_registry_1 = require("./web-socket-registry");
7
+ Object.defineProperty(exports, "WebSocketRegistry", { enumerable: true, get: function () { return web_socket_registry_1.WebSocketRegistry; } });
8
+ var paths_1 = require("./paths");
9
+ Object.defineProperty(exports, "connectionsDatabase", { enumerable: true, get: function () { return paths_1.connectionsDatabase; } });
10
+ Object.defineProperty(exports, "subscriptionsDatabase", { enumerable: true, get: function () { return paths_1.subscriptionsDatabase; } });
11
+ // Export a default instance for easy usage
12
+ const nedb_session_store_adapter_2 = require("./nedb-session-store-adapter");
13
+ exports.sessionStore = new nedb_session_store_adapter_2.NedbSessionStoreAdapter();
@@ -0,0 +1,24 @@
1
+ import { SessionStoreAdapter, MagekConfig, UUID, SubscriptionEnvelope } from '@magek/common';
2
+ export declare class NedbSessionStoreAdapter implements SessionStoreAdapter {
3
+ private connectionRegistry;
4
+ private subscriptionRegistry;
5
+ constructor();
6
+ storeConnection(config: MagekConfig, connectionId: UUID, connectionData: Record<string, any>): Promise<void>;
7
+ fetchConnection(config: MagekConfig, connectionId: UUID): Promise<Record<string, any> | undefined>;
8
+ deleteConnection(config: MagekConfig, connectionId: UUID): Promise<void>;
9
+ storeSubscription(config: MagekConfig, connectionId: UUID, subscriptionId: UUID, subscriptionData: Record<string, any>): Promise<void>;
10
+ fetchSubscription(config: MagekConfig, subscriptionId: UUID): Promise<Record<string, any> | undefined>;
11
+ deleteSubscription(config: MagekConfig, connectionId: UUID, subscriptionId: UUID): Promise<void>;
12
+ fetchSubscriptionsForConnection(config: MagekConfig, connectionId: UUID): Promise<Array<Record<string, any>>>;
13
+ /**
14
+ * Additional method to support fetching subscriptions by className (for read model type)
15
+ * This is needed for the existing subscription system that queries by read model name
16
+ */
17
+ fetchSubscriptionsByClassName(config: MagekConfig, className: string): Promise<Array<SubscriptionEnvelope>>;
18
+ deleteSubscriptionsForConnection(config: MagekConfig, connectionId: UUID): Promise<void>;
19
+ healthCheck: {
20
+ isUp: (config: MagekConfig) => Promise<boolean>;
21
+ details: (config: MagekConfig) => Promise<unknown>;
22
+ urls: (config: MagekConfig) => Promise<Array<string>>;
23
+ };
24
+ }
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NedbSessionStoreAdapter = void 0;
4
+ const common_1 = require("@magek/common");
5
+ const web_socket_registry_1 = require("./web-socket-registry");
6
+ const paths_1 = require("./paths");
7
+ class NedbSessionStoreAdapter {
8
+ constructor() {
9
+ this.healthCheck = {
10
+ isUp: async (config) => {
11
+ try {
12
+ // Test both registries by doing a simple count operation
13
+ await this.connectionRegistry.count();
14
+ await this.subscriptionRegistry.count();
15
+ return true;
16
+ }
17
+ catch (error) {
18
+ return false;
19
+ }
20
+ },
21
+ details: async (config) => {
22
+ try {
23
+ const connectionsCount = await this.connectionRegistry.count();
24
+ const subscriptionsCount = await this.subscriptionRegistry.count();
25
+ return {
26
+ status: 'healthy',
27
+ connections: {
28
+ count: connectionsCount,
29
+ database: (0, paths_1.connectionsDatabase)()
30
+ },
31
+ subscriptions: {
32
+ count: subscriptionsCount,
33
+ database: (0, paths_1.subscriptionsDatabase)()
34
+ }
35
+ };
36
+ }
37
+ catch (error) {
38
+ return {
39
+ status: 'unhealthy',
40
+ error: error instanceof Error ? error.message : 'Unknown error'
41
+ };
42
+ }
43
+ },
44
+ urls: async (config) => {
45
+ return [
46
+ `file://${(0, paths_1.connectionsDatabase)()}`,
47
+ `file://${(0, paths_1.subscriptionsDatabase)()}`
48
+ ];
49
+ }
50
+ };
51
+ this.connectionRegistry = new web_socket_registry_1.WebSocketRegistry((0, paths_1.connectionsDatabase)());
52
+ this.subscriptionRegistry = new web_socket_registry_1.WebSocketRegistry((0, paths_1.subscriptionsDatabase)());
53
+ }
54
+ async storeConnection(config, connectionId, connectionData) {
55
+ const logger = (0, common_1.getLogger)(config, 'NedbSessionStoreAdapter#storeConnection');
56
+ logger.debug('Storing connection data:', { connectionId, connectionData });
57
+ await this.connectionRegistry.store({
58
+ ...connectionData,
59
+ connectionID: connectionId,
60
+ });
61
+ }
62
+ async fetchConnection(config, connectionId) {
63
+ const results = (await this.connectionRegistry.query({
64
+ connectionID: connectionId,
65
+ }));
66
+ if (!results || results.length === 0) {
67
+ return undefined;
68
+ }
69
+ // Remove the internal connectionID and NeDB _id fields before returning
70
+ const { connectionID, _id, ...connectionData } = results[0];
71
+ return connectionData;
72
+ }
73
+ async deleteConnection(config, connectionId) {
74
+ const logger = (0, common_1.getLogger)(config, 'NedbSessionStoreAdapter#deleteConnection');
75
+ const removed = await this.connectionRegistry.delete({ connectionID: connectionId });
76
+ if (removed === 0) {
77
+ logger.info(`No connections found with connectionID=${connectionId}`);
78
+ return;
79
+ }
80
+ logger.debug('Deleted connection:', connectionId);
81
+ }
82
+ async storeSubscription(config, connectionId, subscriptionId, subscriptionData) {
83
+ const logger = (0, common_1.getLogger)(config, 'NedbSessionStoreAdapter#storeSubscription');
84
+ logger.debug('Storing subscription data:', { connectionId, subscriptionId, subscriptionData });
85
+ await this.subscriptionRegistry.store({
86
+ ...subscriptionData,
87
+ connectionID: connectionId,
88
+ subscriptionID: subscriptionId,
89
+ });
90
+ }
91
+ async fetchSubscription(config, subscriptionId) {
92
+ const results = (await this.subscriptionRegistry.query({
93
+ subscriptionID: subscriptionId,
94
+ }));
95
+ if (!results || results.length === 0) {
96
+ return undefined;
97
+ }
98
+ // Remove internal fields and NeDB _id before returning
99
+ const { connectionID, subscriptionID, _id, ...subscriptionData } = results[0];
100
+ return subscriptionData;
101
+ }
102
+ async deleteSubscription(config, connectionId, subscriptionId) {
103
+ const logger = (0, common_1.getLogger)(config, 'NedbSessionStoreAdapter#deleteSubscription');
104
+ const removed = await this.subscriptionRegistry.delete({
105
+ connectionID: connectionId,
106
+ subscriptionID: subscriptionId
107
+ });
108
+ if (removed === 0) {
109
+ logger.info(`No subscription found with connectionID=${connectionId} and subscriptionID=${subscriptionId}`);
110
+ return;
111
+ }
112
+ logger.debug('Deleted subscription:', { connectionId, subscriptionId });
113
+ }
114
+ async fetchSubscriptionsForConnection(config, connectionId) {
115
+ const results = (await this.subscriptionRegistry.query({
116
+ connectionID: connectionId,
117
+ }));
118
+ // Remove internal fields and NeDB _id from each subscription
119
+ return results.map(({ connectionID, subscriptionID, _id, ...subscriptionData }) => subscriptionData);
120
+ }
121
+ /**
122
+ * Additional method to support fetching subscriptions by className (for read model type)
123
+ * This is needed for the existing subscription system that queries by read model name
124
+ */
125
+ async fetchSubscriptionsByClassName(config, className) {
126
+ const results = (await this.subscriptionRegistry.query({
127
+ className: className,
128
+ }));
129
+ // Remove internal fields and NeDB _id from each subscription
130
+ return results.map(({ _id, ...subscriptionData }) => subscriptionData);
131
+ }
132
+ async deleteSubscriptionsForConnection(config, connectionId) {
133
+ const logger = (0, common_1.getLogger)(config, 'NedbSessionStoreAdapter#deleteSubscriptionsForConnection');
134
+ const removed = await this.subscriptionRegistry.delete({ connectionID: connectionId });
135
+ logger.debug(`Deleted ${removed} subscriptions for connection:`, connectionId);
136
+ }
137
+ }
138
+ exports.NedbSessionStoreAdapter = NedbSessionStoreAdapter;
@@ -0,0 +1,2 @@
1
+ export declare function connectionsDatabase(): string;
2
+ export declare function subscriptionsDatabase(): string;
package/dist/paths.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.connectionsDatabase = connectionsDatabase;
4
+ exports.subscriptionsDatabase = subscriptionsDatabase;
5
+ // Path helpers for NeDB session store databases
6
+ const path = require("path");
7
+ function connectionsDatabase() {
8
+ return internalPath('connections.json');
9
+ }
10
+ function subscriptionsDatabase() {
11
+ return internalPath('subscriptions.json');
12
+ }
13
+ function internalPath(filename) {
14
+ return path.normalize(path.join('.', '.magek', filename));
15
+ }
@@ -0,0 +1,24 @@
1
+ import { UUID } from '@magek/common';
2
+ export interface ConnectionRecord {
3
+ connectionID: UUID;
4
+ [key: string]: any;
5
+ }
6
+ export interface SubscriptionRecord {
7
+ connectionID: UUID;
8
+ subscriptionID: UUID;
9
+ [key: string]: any;
10
+ }
11
+ export type RegistryRecord = ConnectionRecord | SubscriptionRecord;
12
+ export declare class WebSocketRegistry {
13
+ datastore: any;
14
+ isLoaded: boolean;
15
+ constructor(databasePath: string);
16
+ loadDatabaseIfNeeded(): Promise<void>;
17
+ addIndexes(): Promise<void>;
18
+ getCursor(query: object, createdAt?: number, projections?: unknown): any;
19
+ query(query: object, createdAt?: number, limit?: number, projections?: unknown): Promise<unknown>;
20
+ store(envelope: Record<string, any>): Promise<void>;
21
+ delete(query: unknown): Promise<number>;
22
+ deleteAll(): Promise<number>;
23
+ count(query?: object): Promise<number>;
24
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketRegistry = void 0;
4
+ const DataStore = require('@seald-io/nedb');
5
+ class WebSocketRegistry {
6
+ constructor(databasePath) {
7
+ this.isLoaded = false;
8
+ this.datastore = new DataStore({ filename: databasePath });
9
+ }
10
+ async loadDatabaseIfNeeded() {
11
+ if (!this.isLoaded) {
12
+ this.isLoaded = true;
13
+ await this.datastore.loadDatabaseAsync();
14
+ await this.addIndexes();
15
+ }
16
+ }
17
+ async addIndexes() {
18
+ const maxDurationInSeconds = 2 * 24 * 60 * 60; // 2 days
19
+ this.datastore.ensureIndexAsync({ fieldName: 'expirationTime', expireAfterSeconds: maxDurationInSeconds });
20
+ }
21
+ getCursor(query, createdAt = 1, projections) {
22
+ return this.datastore.findAsync(query, projections).sort({ createdAt: createdAt });
23
+ }
24
+ async query(query, createdAt = 1, limit, projections) {
25
+ await this.loadDatabaseIfNeeded();
26
+ let cursor = this.getCursor(query, createdAt, projections);
27
+ if (limit) {
28
+ cursor = cursor.limit(Number(limit));
29
+ }
30
+ return await cursor.execAsync();
31
+ }
32
+ async store(envelope) {
33
+ await this.loadDatabaseIfNeeded();
34
+ await this.datastore.insertAsync(envelope);
35
+ }
36
+ async delete(query) {
37
+ await this.loadDatabaseIfNeeded();
38
+ return await this.datastore.removeAsync(query, { multi: true });
39
+ }
40
+ async deleteAll() {
41
+ await this.loadDatabaseIfNeeded();
42
+ return await this.datastore.removeAsync({}, { multi: true });
43
+ }
44
+ async count(query) {
45
+ await this.loadDatabaseIfNeeded();
46
+ return await this.datastore.countAsync(query);
47
+ }
48
+ }
49
+ exports.WebSocketRegistry = WebSocketRegistry;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@magek/adapter-session-store-nedb",
3
+ "version": "0.0.1",
4
+ "description": "Nedb-based session store adapter for the Magek framework",
5
+ "keywords": [
6
+ "session-store",
7
+ "nedb",
8
+ "websocket",
9
+ "connections",
10
+ "subscriptions"
11
+ ],
12
+ "author": "Boosterin Labs SLU",
13
+ "homepage": "https://magek.ai",
14
+ "license": "Apache-2.0",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "main": "dist/index.js",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/theam/magek.git"
25
+ },
26
+ "engines": {
27
+ "node": ">=22.0.0 <23.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@magek/common": "^0.0.1",
31
+ "@seald-io/nedb": "4.1.2",
32
+ "tslib": "2.8.1"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/theam/magek/issues"
36
+ },
37
+ "devDependencies": {
38
+ "@magek/eslint-config": "^0.0.1",
39
+ "@types/chai": "5.2.3",
40
+ "@types/chai-as-promised": "8.0.2",
41
+ "@types/mocha": "10.0.10",
42
+ "@types/node": "22.19.3",
43
+ "@types/sinon": "21.0.0",
44
+ "@types/sinon-chai": "4.0.0",
45
+ "chai": "6.2.2",
46
+ "chai-as-promised": "8.0.2",
47
+ "@faker-js/faker": "10.2.0",
48
+ "mocha": "11.7.5",
49
+ "c8": "^10.1.3",
50
+ "rimraf": "6.1.2",
51
+ "sinon": "21.0.1",
52
+ "sinon-chai": "4.0.1",
53
+ "tsx": "^4.19.2",
54
+ "typescript": "5.9.3",
55
+ "memfs": "~4.36.0"
56
+ },
57
+ "scripts": {
58
+ "format": "prettier --write --ext '.js,.ts' **/*.ts **/*/*.ts",
59
+ "lint:check": "eslint \"**/*.ts\"",
60
+ "lint:fix": "eslint --quiet --fix \"**/*.ts\"",
61
+ "build": "tsc -b tsconfig.json",
62
+ "clean": "rimraf ./dist tsconfig.tsbuildinfo",
63
+ "test": "tsc --noEmit -p tsconfig.test.json && c8 mocha --forbid-only \"test/**/*.test.ts\""
64
+ }
65
+ }