@rebasepro/server-mongo 0.0.1-canary.4829d6e
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 +22 -0
- package/README.md +86 -0
- package/dist/MongoBootstrapper.d.ts +18 -0
- package/dist/MongoBootstrapper.d.ts.map +1 -0
- package/dist/auth/ensure-collections.d.ts +3 -0
- package/dist/auth/ensure-collections.d.ts.map +1 -0
- package/dist/auth/services.d.ts +156 -0
- package/dist/auth/services.d.ts.map +1 -0
- package/dist/connection.d.ts +35 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/db/MongoConditionBuilder.d.ts +64 -0
- package/dist/db/MongoConditionBuilder.d.ts.map +1 -0
- package/dist/db/MongoDataService.d.ts +101 -0
- package/dist/db/MongoDataService.d.ts.map +1 -0
- package/dist/ensure-collections-Bkx_O5CQ.js +94 -0
- package/dist/ensure-collections-Bkx_O5CQ.js.map +1 -0
- package/dist/ensure-history-collection-yajOt2dv.js +21 -0
- package/dist/ensure-history-collection-yajOt2dv.js.map +1 -0
- package/dist/factory.d.ts +151 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/history/ensure-history-collection.d.ts +3 -0
- package/dist/history/ensure-history-collection.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +2508 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2925 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/services/MongoDriver.d.ts +125 -0
- package/dist/services/MongoDriver.d.ts.map +1 -0
- package/dist/services/MongoHistoryService.d.ts +37 -0
- package/dist/services/MongoHistoryService.d.ts.map +1 -0
- package/dist/services/MongoRealtimeService.d.ts +103 -0
- package/dist/services/MongoRealtimeService.d.ts.map +1 -0
- package/dist/websocket-DQlwCHFq.js +281 -0
- package/dist/websocket-DQlwCHFq.js.map +1 -0
- package/dist/websocket.d.ts +7 -0
- package/dist/websocket.d.ts.map +1 -0
- package/package.json +81 -0
- package/src/MongoBootstrapper.ts +196 -0
- package/src/auth/ensure-collections.ts +105 -0
- package/src/auth/services.ts +732 -0
- package/src/connection.ts +60 -0
- package/src/db/MongoConditionBuilder.ts +224 -0
- package/src/db/MongoDataService.ts +368 -0
- package/src/factory.ts +305 -0
- package/src/history/ensure-history-collection.ts +22 -0
- package/src/index.ts +25 -0
- package/src/services/MongoDriver.ts +1120 -0
- package/src/services/MongoHistoryService.ts +181 -0
- package/src/services/MongoRealtimeService.ts +446 -0
- package/src/websocket.ts +297 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Db, MongoClient } from "mongodb";
|
|
2
|
+
import type {
|
|
3
|
+
BackendBootstrapper,
|
|
4
|
+
InitializedDriver,
|
|
5
|
+
BootstrappedAuth,
|
|
6
|
+
DatabaseAdmin,
|
|
7
|
+
HistoryConfig,
|
|
8
|
+
RealtimeProvider,
|
|
9
|
+
DataDriver,
|
|
10
|
+
CollectionConfig
|
|
11
|
+
} from "@rebasepro/types";
|
|
12
|
+
import { MongoDriver } from "./services/MongoDriver";
|
|
13
|
+
import { MongoRealtimeService } from "./services/MongoRealtimeService";
|
|
14
|
+
import { MongoCollectionRegistry } from "./factory";
|
|
15
|
+
import { MongoAuthRepository, MongoUserService, MongoRoleService } from "./auth/services";
|
|
16
|
+
import { logger } from "@rebasepro/server";
|
|
17
|
+
|
|
18
|
+
export interface MongoDriverConfig {
|
|
19
|
+
connection: Db;
|
|
20
|
+
client: MongoClient;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MongoDriverInternals {
|
|
24
|
+
db: Db;
|
|
25
|
+
client: MongoClient;
|
|
26
|
+
registry: MongoCollectionRegistry;
|
|
27
|
+
realtimeService: MongoRealtimeService;
|
|
28
|
+
driver: MongoDriver;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Shape of the config object passed to `initializeDriver` by the coordinator. */
|
|
32
|
+
interface DriverInitConfig {
|
|
33
|
+
collections?: CollectionConfig[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Shape of the auth config passed to `initializeAuth`. */
|
|
37
|
+
interface AuthInitConfig {
|
|
38
|
+
email?: import("@rebasepro/server").EmailConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export function createMongoBootstrapper(mongoConfig: MongoDriverConfig): BackendBootstrapper {
|
|
44
|
+
// Cached admin object, set during getAdmin() and used by initializeWebsockets
|
|
45
|
+
let cachedAdmin: DatabaseAdmin | undefined;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
type: "mongodb",
|
|
49
|
+
|
|
50
|
+
async initializeDriver(config: unknown): Promise<InitializedDriver> {
|
|
51
|
+
const { collections } = config as DriverInitConfig;
|
|
52
|
+
|
|
53
|
+
const registry = new MongoCollectionRegistry();
|
|
54
|
+
if (collections) {
|
|
55
|
+
collections.forEach(collection => registry.register(collection));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const db = mongoConfig.connection;
|
|
59
|
+
const client = mongoConfig.client;
|
|
60
|
+
|
|
61
|
+
// Verify connection
|
|
62
|
+
try {
|
|
63
|
+
await db.command({ ping: 1 });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error("❌ Failed to connect to MongoDB", { error: err });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const realtimeService = new MongoRealtimeService(db);
|
|
69
|
+
const driver = new MongoDriver(db, realtimeService, undefined, registry);
|
|
70
|
+
|
|
71
|
+
const internals: MongoDriverInternals = {
|
|
72
|
+
db,
|
|
73
|
+
client,
|
|
74
|
+
registry,
|
|
75
|
+
realtimeService,
|
|
76
|
+
driver
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
driver,
|
|
81
|
+
realtimeProvider: realtimeService,
|
|
82
|
+
collectionRegistry: registry,
|
|
83
|
+
internals
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async initializeAuth(config: unknown, driverResult: InitializedDriver): Promise<BootstrappedAuth | undefined> {
|
|
88
|
+
const internals = driverResult.internals as MongoDriverInternals;
|
|
89
|
+
const db = internals.db;
|
|
90
|
+
|
|
91
|
+
const { ensureAuthCollectionsExist } = await import("./auth/ensure-collections");
|
|
92
|
+
await ensureAuthCollectionsExist(db);
|
|
93
|
+
|
|
94
|
+
const { createEmailService } = await import("@rebasepro/server");
|
|
95
|
+
const authConfig = config as AuthInitConfig | undefined;
|
|
96
|
+
let emailService: unknown;
|
|
97
|
+
if (authConfig?.email) {
|
|
98
|
+
emailService = createEmailService(authConfig.email);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const userService = new MongoUserService(db);
|
|
102
|
+
const roleService = new MongoRoleService(db);
|
|
103
|
+
const authRepository = new MongoAuthRepository(db);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
userService,
|
|
107
|
+
roleService,
|
|
108
|
+
authRepository,
|
|
109
|
+
emailService
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async initializeHistory(config: HistoryConfig, driverResult: InitializedDriver): Promise<{ historyService: unknown } | undefined> {
|
|
114
|
+
if (!config) return undefined;
|
|
115
|
+
|
|
116
|
+
const internals = driverResult.internals as MongoDriverInternals;
|
|
117
|
+
const db = internals.db;
|
|
118
|
+
|
|
119
|
+
const { ensureHistoryCollectionExists } = await import("./history/ensure-history-collection");
|
|
120
|
+
await ensureHistoryCollectionExists(db);
|
|
121
|
+
|
|
122
|
+
const { MongoHistoryService } = await import("./services/MongoHistoryService");
|
|
123
|
+
|
|
124
|
+
const retention = typeof config === "object" ? config.retention : undefined;
|
|
125
|
+
const historyService = new MongoHistoryService(db, retention ? { ttlDays: retention } : undefined);
|
|
126
|
+
|
|
127
|
+
return { historyService };
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async initializeRealtime(_config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined> {
|
|
131
|
+
const internals = driverResult.internals as MongoDriverInternals;
|
|
132
|
+
return internals.realtimeService;
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
getAdmin(driverResult: InitializedDriver): DatabaseAdmin | undefined {
|
|
136
|
+
const internals = driverResult.internals as MongoDriverInternals;
|
|
137
|
+
const db = internals.db;
|
|
138
|
+
|
|
139
|
+
const admin: DatabaseAdmin = {
|
|
140
|
+
async executeAggregate(pipeline: Record<string, unknown>[]) {
|
|
141
|
+
const firstStage = pipeline[0];
|
|
142
|
+
const collName = (firstStage as { $from?: string })?.$from ?? "__admin__";
|
|
143
|
+
const cursor = db.collection(collName).aggregate(pipeline);
|
|
144
|
+
return await cursor.toArray() as Record<string, unknown>[];
|
|
145
|
+
},
|
|
146
|
+
async fetchCollectionStats(collectionName: string) {
|
|
147
|
+
const stats = await db.command({ collStats: collectionName }) as { count: number; size: number };
|
|
148
|
+
return { count: stats.count,
|
|
149
|
+
sizeBytes: stats.size };
|
|
150
|
+
},
|
|
151
|
+
async fetchUnmappedTables(mappedPaths?: string[]) {
|
|
152
|
+
const allCollections = await db.listCollections().toArray();
|
|
153
|
+
const names = allCollections.map(c => c.name).filter(n => !n.startsWith("system."));
|
|
154
|
+
if (!mappedPaths || mappedPaths.length === 0) return names;
|
|
155
|
+
const mappedSet = new Set(mappedPaths.map(p => p.toLowerCase()));
|
|
156
|
+
return names.filter(n => !mappedSet.has(n.toLowerCase()));
|
|
157
|
+
},
|
|
158
|
+
async fetchTableMetadata(collectionName: string) {
|
|
159
|
+
const sample = await db.collection(collectionName).findOne();
|
|
160
|
+
if (!sample) return { columns: [],
|
|
161
|
+
foreignKeys: [],
|
|
162
|
+
junctions: [],
|
|
163
|
+
policies: [] };
|
|
164
|
+
const columns = Object.entries(sample).map(([key, value]) => ({
|
|
165
|
+
column_name: key,
|
|
166
|
+
data_type: typeof value,
|
|
167
|
+
udt_name: typeof value,
|
|
168
|
+
is_nullable: "YES",
|
|
169
|
+
column_default: null,
|
|
170
|
+
character_maximum_length: null
|
|
171
|
+
}));
|
|
172
|
+
return { columns,
|
|
173
|
+
foreignKeys: [],
|
|
174
|
+
junctions: [],
|
|
175
|
+
policies: [] };
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
cachedAdmin = admin;
|
|
180
|
+
return admin;
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
mountRoutes() {},
|
|
184
|
+
|
|
185
|
+
async initializeWebsockets(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown): Promise<void> {
|
|
186
|
+
const { createMongoWebSocket } = await import("./websocket");
|
|
187
|
+
createMongoWebSocket(
|
|
188
|
+
server as import("http").Server,
|
|
189
|
+
realtimeService as MongoRealtimeService,
|
|
190
|
+
driver as MongoDriver,
|
|
191
|
+
config as Record<string, unknown> | undefined,
|
|
192
|
+
cachedAdmin
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Db } from "mongodb";
|
|
2
|
+
import { logger } from "@rebasepro/server";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default roles to seed on first run
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_ROLES = [
|
|
8
|
+
{
|
|
9
|
+
_id: "admin",
|
|
10
|
+
id: "admin",
|
|
11
|
+
name: "Admin",
|
|
12
|
+
isAdmin: true,
|
|
13
|
+
defaultPermissions: { read: true,
|
|
14
|
+
create: true,
|
|
15
|
+
edit: true,
|
|
16
|
+
delete: true },
|
|
17
|
+
createdAt: new Date()
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
_id: "editor",
|
|
21
|
+
id: "editor",
|
|
22
|
+
name: "Editor",
|
|
23
|
+
isAdmin: false,
|
|
24
|
+
defaultPermissions: { read: true,
|
|
25
|
+
create: true,
|
|
26
|
+
edit: true,
|
|
27
|
+
delete: true },
|
|
28
|
+
createdAt: new Date()
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
_id: "viewer",
|
|
32
|
+
id: "viewer",
|
|
33
|
+
name: "Viewer",
|
|
34
|
+
isAdmin: false,
|
|
35
|
+
defaultPermissions: { read: true,
|
|
36
|
+
create: false,
|
|
37
|
+
edit: false,
|
|
38
|
+
delete: false },
|
|
39
|
+
createdAt: new Date()
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export async function ensureAuthCollectionsExist(db: Db): Promise<void> {
|
|
44
|
+
logger.info("🔍 Checking MongoDB auth collections and indexes...");
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Users
|
|
48
|
+
const users = db.collection("rebase_users");
|
|
49
|
+
await users.createIndex({ email: 1 }, { unique: true });
|
|
50
|
+
|
|
51
|
+
// User Identities
|
|
52
|
+
const identities = db.collection("rebase_user_identities");
|
|
53
|
+
await identities.createIndex({ provider: 1,
|
|
54
|
+
provider_id: 1 }, { unique: true });
|
|
55
|
+
await identities.createIndex({ user_id: 1 });
|
|
56
|
+
|
|
57
|
+
// User Roles (junction collection)
|
|
58
|
+
const userRoles = db.collection("rebase_user_roles");
|
|
59
|
+
await userRoles.createIndex({ user_id: 1,
|
|
60
|
+
role_id: 1 }, { unique: true });
|
|
61
|
+
await userRoles.createIndex({ user_id: 1 });
|
|
62
|
+
|
|
63
|
+
// Refresh Tokens
|
|
64
|
+
const refreshTokens = db.collection("rebase_refresh_tokens");
|
|
65
|
+
await refreshTokens.createIndex({ token_hash: 1 }, { unique: true });
|
|
66
|
+
await refreshTokens.createIndex({ user_id: 1,
|
|
67
|
+
user_agent: 1,
|
|
68
|
+
ip_address: 1 }, { unique: true });
|
|
69
|
+
await refreshTokens.createIndex({ user_id: 1 });
|
|
70
|
+
|
|
71
|
+
// Password Reset Tokens
|
|
72
|
+
const resetTokens = db.collection("rebase_password_reset_tokens");
|
|
73
|
+
await resetTokens.createIndex({ token_hash: 1 }, { unique: true });
|
|
74
|
+
await resetTokens.createIndex({ user_id: 1 });
|
|
75
|
+
|
|
76
|
+
// Seed roles
|
|
77
|
+
await seedDefaultRoles(db);
|
|
78
|
+
|
|
79
|
+
logger.info("✅ MongoDB Auth collections ready");
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error("❌ Failed to set up MongoDB auth collections", { error: error });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function seedDefaultRoles(db: Db): Promise<void> {
|
|
86
|
+
const roles = db.collection("rebase_roles");
|
|
87
|
+
const count = await roles.countDocuments();
|
|
88
|
+
|
|
89
|
+
if (count > 0) {
|
|
90
|
+
logger.info(`📋 Found ${count} existing roles`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logger.info("🌱 Seeding default roles...");
|
|
95
|
+
|
|
96
|
+
for (const role of DEFAULT_ROLES) {
|
|
97
|
+
await roles.updateOne(
|
|
98
|
+
{ id: role.id },
|
|
99
|
+
{ $setOnInsert: role },
|
|
100
|
+
{ upsert: true }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
logger.info("✅ Default roles created: admin, editor, viewer");
|
|
105
|
+
}
|