@almadar/server 1.0.13 → 1.0.15
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--XhXIuTh.d.ts → index-BGC2yptv.d.ts} +12 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.js +583 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +43 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/middleware/index.js +21 -1
- package/dist/middleware/index.js.map +1 -1
- package/dist/stores/index.d.ts +167 -0
- package/dist/stores/index.js +594 -0
- package/dist/stores/index.js.map +1 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { z, ZodError } from 'zod';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
|
+
import { Router } from 'express';
|
|
3
4
|
import admin from 'firebase-admin';
|
|
4
5
|
export { default as admin } from 'firebase-admin';
|
|
5
6
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
6
7
|
import { faker } from '@faker-js/faker';
|
|
8
|
+
import { diffSchemas, categorizeRemovals, detectPageContentReduction, isDestructiveChange, hasSignificantPageReduction, requiresConfirmation } from '@almadar/core';
|
|
7
9
|
|
|
8
10
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
11
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -86,9 +88,11 @@ var logger = {
|
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
// src/lib/eventBus.ts
|
|
91
|
+
var MAX_EVENT_LOG = 200;
|
|
89
92
|
var EventBus = class {
|
|
90
93
|
handlers = /* @__PURE__ */ new Map();
|
|
91
94
|
debug;
|
|
95
|
+
eventLog = [];
|
|
92
96
|
constructor(options) {
|
|
93
97
|
this.debug = options?.debug ?? false;
|
|
94
98
|
}
|
|
@@ -109,6 +113,7 @@ var EventBus = class {
|
|
|
109
113
|
console.log(`[EventBus] Emitting ${event}:`, payload);
|
|
110
114
|
}
|
|
111
115
|
const handlers = this.handlers.get(event);
|
|
116
|
+
const listenerCount = handlers?.size ?? 0;
|
|
112
117
|
if (handlers) {
|
|
113
118
|
handlers.forEach((handler) => {
|
|
114
119
|
try {
|
|
@@ -118,9 +123,49 @@ var EventBus = class {
|
|
|
118
123
|
}
|
|
119
124
|
});
|
|
120
125
|
}
|
|
126
|
+
let wildcardListenerCount = 0;
|
|
127
|
+
if (event !== "*") {
|
|
128
|
+
const wildcardHandlers = this.handlers.get("*");
|
|
129
|
+
wildcardListenerCount = wildcardHandlers?.size ?? 0;
|
|
130
|
+
if (wildcardHandlers) {
|
|
131
|
+
wildcardHandlers.forEach((handler) => {
|
|
132
|
+
try {
|
|
133
|
+
handler({ type: event, payload, timestamp: Date.now() }, meta);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error(`[EventBus] Error in wildcard handler for ${event}:`, err);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (this.debug) {
|
|
141
|
+
this.eventLog.push({
|
|
142
|
+
event,
|
|
143
|
+
payload,
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
listenerCount,
|
|
146
|
+
wildcardListenerCount
|
|
147
|
+
});
|
|
148
|
+
if (this.eventLog.length > MAX_EVENT_LOG) {
|
|
149
|
+
this.eventLog.splice(0, this.eventLog.length - MAX_EVENT_LOG);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
getRecentEvents(limit = 50) {
|
|
154
|
+
return this.eventLog.slice(-limit);
|
|
155
|
+
}
|
|
156
|
+
clearEventLog() {
|
|
157
|
+
this.eventLog.length = 0;
|
|
158
|
+
}
|
|
159
|
+
getListenerCounts() {
|
|
160
|
+
const counts = {};
|
|
161
|
+
this.handlers.forEach((handlers, event) => {
|
|
162
|
+
counts[event] = handlers.size;
|
|
163
|
+
});
|
|
164
|
+
return counts;
|
|
121
165
|
}
|
|
122
166
|
clear() {
|
|
123
167
|
this.handlers.clear();
|
|
168
|
+
this.eventLog.length = 0;
|
|
124
169
|
}
|
|
125
170
|
};
|
|
126
171
|
var serverEventBus = new EventBus({
|
|
@@ -130,6 +175,27 @@ function emitEntityEvent(entityType, action, payload) {
|
|
|
130
175
|
const eventType = `${entityType.toUpperCase()}_${action}`;
|
|
131
176
|
serverEventBus.emit(eventType, payload, { orbital: entityType });
|
|
132
177
|
}
|
|
178
|
+
function debugEventsRouter() {
|
|
179
|
+
const router = Router();
|
|
180
|
+
if (process.env.NODE_ENV !== "development") {
|
|
181
|
+
return router;
|
|
182
|
+
}
|
|
183
|
+
router.get("/event-log", (_req, res) => {
|
|
184
|
+
const limit = parseInt(String(_req.query.limit) || "50", 10);
|
|
185
|
+
const events = serverEventBus.getRecentEvents(limit);
|
|
186
|
+
res.json({ count: events.length, events });
|
|
187
|
+
});
|
|
188
|
+
router.delete("/event-log", (_req, res) => {
|
|
189
|
+
serverEventBus.clearEventLog();
|
|
190
|
+
res.json({ cleared: true });
|
|
191
|
+
});
|
|
192
|
+
router.get("/listeners", (_req, res) => {
|
|
193
|
+
const counts = serverEventBus.getListenerCounts();
|
|
194
|
+
const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
|
|
195
|
+
res.json({ total, events: counts });
|
|
196
|
+
});
|
|
197
|
+
return router;
|
|
198
|
+
}
|
|
133
199
|
var firebaseApp = null;
|
|
134
200
|
function initializeFirebase() {
|
|
135
201
|
if (firebaseApp) {
|
|
@@ -417,9 +483,29 @@ var validateParams = (schema) => async (req, res, next) => {
|
|
|
417
483
|
|
|
418
484
|
// src/middleware/authenticateFirebase.ts
|
|
419
485
|
var BEARER_PREFIX = "Bearer ";
|
|
486
|
+
var DEV_USER = {
|
|
487
|
+
uid: "dev-user-001",
|
|
488
|
+
email: "dev@localhost",
|
|
489
|
+
email_verified: true,
|
|
490
|
+
aud: "dev-project",
|
|
491
|
+
auth_time: Math.floor(Date.now() / 1e3),
|
|
492
|
+
exp: Math.floor(Date.now() / 1e3) + 3600,
|
|
493
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
494
|
+
iss: "https://securetoken.google.com/dev-project",
|
|
495
|
+
sub: "dev-user-001",
|
|
496
|
+
firebase: {
|
|
497
|
+
identities: {},
|
|
498
|
+
sign_in_provider: "custom"
|
|
499
|
+
}
|
|
500
|
+
};
|
|
420
501
|
async function authenticateFirebase(req, res, next) {
|
|
502
|
+
const authorization = req.headers.authorization;
|
|
503
|
+
if (env.NODE_ENV === "development" && (!authorization || !authorization.startsWith(BEARER_PREFIX))) {
|
|
504
|
+
req.firebaseUser = DEV_USER;
|
|
505
|
+
res.locals.firebaseUser = DEV_USER;
|
|
506
|
+
return next();
|
|
507
|
+
}
|
|
421
508
|
try {
|
|
422
|
-
const authorization = req.headers.authorization;
|
|
423
509
|
if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
|
|
424
510
|
return res.status(401).json({ error: "Authorization header missing or malformed" });
|
|
425
511
|
}
|
|
@@ -1010,6 +1096,501 @@ function seedMockData(entities) {
|
|
|
1010
1096
|
logger.info("[DataService] Mock data seeding complete");
|
|
1011
1097
|
}
|
|
1012
1098
|
|
|
1013
|
-
|
|
1099
|
+
// src/stores/firestoreFormat.ts
|
|
1100
|
+
function toFirestoreFormat(schema) {
|
|
1101
|
+
const data = { ...schema };
|
|
1102
|
+
if (schema.orbitals) {
|
|
1103
|
+
data._orbitalsJson = JSON.stringify(schema.orbitals);
|
|
1104
|
+
data.orbitalCount = schema.orbitals.length;
|
|
1105
|
+
delete data.orbitals;
|
|
1106
|
+
}
|
|
1107
|
+
if (data.traits) {
|
|
1108
|
+
const traits = data.traits;
|
|
1109
|
+
data._traitsJson = JSON.stringify(traits);
|
|
1110
|
+
data.traitCount = traits.length;
|
|
1111
|
+
delete data.traits;
|
|
1112
|
+
}
|
|
1113
|
+
if (schema.services) {
|
|
1114
|
+
data._servicesJson = JSON.stringify(schema.services);
|
|
1115
|
+
data.serviceCount = schema.services.length;
|
|
1116
|
+
delete data.services;
|
|
1117
|
+
}
|
|
1118
|
+
return data;
|
|
1119
|
+
}
|
|
1120
|
+
function fromFirestoreFormat(data) {
|
|
1121
|
+
const result = { ...data };
|
|
1122
|
+
if (result._orbitalsJson && typeof result._orbitalsJson === "string") {
|
|
1123
|
+
try {
|
|
1124
|
+
result.orbitals = JSON.parse(result._orbitalsJson);
|
|
1125
|
+
delete result._orbitalsJson;
|
|
1126
|
+
delete result.orbitalCount;
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
console.warn("[OrbitalStore] Failed to parse _orbitalsJson:", e);
|
|
1129
|
+
result.orbitals = [];
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (result._traitsJson && typeof result._traitsJson === "string") {
|
|
1133
|
+
try {
|
|
1134
|
+
result.traits = JSON.parse(result._traitsJson);
|
|
1135
|
+
delete result._traitsJson;
|
|
1136
|
+
delete result.traitCount;
|
|
1137
|
+
} catch (e) {
|
|
1138
|
+
console.warn("[OrbitalStore] Failed to parse _traitsJson:", e);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (result._servicesJson && typeof result._servicesJson === "string") {
|
|
1142
|
+
try {
|
|
1143
|
+
result.services = JSON.parse(result._servicesJson);
|
|
1144
|
+
delete result._servicesJson;
|
|
1145
|
+
delete result.serviceCount;
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
console.warn("[OrbitalStore] Failed to parse _servicesJson:", e);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return result;
|
|
1151
|
+
}
|
|
1152
|
+
var SchemaProtectionService = class {
|
|
1153
|
+
/**
|
|
1154
|
+
* Compare two schemas and detect destructive changes.
|
|
1155
|
+
*
|
|
1156
|
+
* Returns categorized removals including page content reductions.
|
|
1157
|
+
*/
|
|
1158
|
+
compareSchemas(before, after) {
|
|
1159
|
+
const changeSet = diffSchemas(before, after);
|
|
1160
|
+
const removals = categorizeRemovals(changeSet);
|
|
1161
|
+
const beforePages = before.orbitals?.flatMap((o) => o.pages || []) || [];
|
|
1162
|
+
const afterPages = after.orbitals?.flatMap((o) => o.pages || []) || [];
|
|
1163
|
+
const pageContentReductions = detectPageContentReduction(beforePages, afterPages);
|
|
1164
|
+
removals.pageContentReductions = pageContentReductions;
|
|
1165
|
+
const isDestructive = isDestructiveChange(changeSet) || hasSignificantPageReduction(pageContentReductions);
|
|
1166
|
+
return { isDestructive, removals };
|
|
1167
|
+
}
|
|
1168
|
+
/** Check if critical removals require confirmation */
|
|
1169
|
+
requiresConfirmation(removals) {
|
|
1170
|
+
return requiresConfirmation(removals);
|
|
1171
|
+
}
|
|
1172
|
+
/** Check for significant page content reductions */
|
|
1173
|
+
hasSignificantContentReduction(reductions) {
|
|
1174
|
+
return hasSignificantPageReduction(reductions);
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
// src/stores/SchemaStore.ts
|
|
1179
|
+
var SCHEMA_CACHE_TTL_MS = 6e4;
|
|
1180
|
+
var LIST_CACHE_TTL_MS = 3e4;
|
|
1181
|
+
var SchemaStore = class {
|
|
1182
|
+
appsCollection;
|
|
1183
|
+
schemaCache = /* @__PURE__ */ new Map();
|
|
1184
|
+
listCache = /* @__PURE__ */ new Map();
|
|
1185
|
+
protectionService = new SchemaProtectionService();
|
|
1186
|
+
snapshotStore = null;
|
|
1187
|
+
constructor(appsCollection = "apps") {
|
|
1188
|
+
this.appsCollection = appsCollection;
|
|
1189
|
+
}
|
|
1190
|
+
/** Set snapshot store for auto-snapshot on destructive saves */
|
|
1191
|
+
setSnapshotStore(store) {
|
|
1192
|
+
this.snapshotStore = store;
|
|
1193
|
+
}
|
|
1194
|
+
/** Get a schema by app ID */
|
|
1195
|
+
async get(uid, appId) {
|
|
1196
|
+
const cacheKey = `${uid}:${appId}`;
|
|
1197
|
+
const cached = this.schemaCache.get(cacheKey);
|
|
1198
|
+
if (cached && Date.now() - cached.timestamp < SCHEMA_CACHE_TTL_MS) {
|
|
1199
|
+
return cached.schema;
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
const db2 = getFirestore();
|
|
1203
|
+
const appDoc = await db2.doc(`users/${uid}/${this.appsCollection}/${appId}`).get();
|
|
1204
|
+
if (!appDoc.exists) return null;
|
|
1205
|
+
const data = appDoc.data();
|
|
1206
|
+
const hasOrbitals = data.orbitals || data._orbitalsJson;
|
|
1207
|
+
if (!data.name || !hasOrbitals) return null;
|
|
1208
|
+
const schema = fromFirestoreFormat(data);
|
|
1209
|
+
this.schemaCache.set(cacheKey, { schema, timestamp: Date.now() });
|
|
1210
|
+
return schema;
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
console.error("[SchemaStore] Error fetching schema:", error);
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Save a schema (create or full replace).
|
|
1218
|
+
*
|
|
1219
|
+
* Features:
|
|
1220
|
+
* - Detects destructive changes (removals)
|
|
1221
|
+
* - Requires confirmation for critical removals
|
|
1222
|
+
* - Auto-creates snapshots before destructive changes (if SnapshotStore attached)
|
|
1223
|
+
*/
|
|
1224
|
+
async save(uid, appId, schema, options = {}) {
|
|
1225
|
+
try {
|
|
1226
|
+
const existingSchema = await this.get(uid, appId);
|
|
1227
|
+
let snapshotId;
|
|
1228
|
+
if (existingSchema && options.snapshotReason && this.snapshotStore) {
|
|
1229
|
+
snapshotId = await this.snapshotStore.create(uid, appId, existingSchema, options.snapshotReason);
|
|
1230
|
+
}
|
|
1231
|
+
if (existingSchema && !options.skipProtection) {
|
|
1232
|
+
const comparison = this.protectionService.compareSchemas(existingSchema, schema);
|
|
1233
|
+
if (comparison.isDestructive) {
|
|
1234
|
+
const { removals } = comparison;
|
|
1235
|
+
const hasCriticalRemovals = this.protectionService.requiresConfirmation(removals);
|
|
1236
|
+
const hasContentReductions = this.protectionService.hasSignificantContentReduction(
|
|
1237
|
+
removals.pageContentReductions
|
|
1238
|
+
);
|
|
1239
|
+
if ((hasCriticalRemovals || hasContentReductions) && !options.confirmRemovals) {
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
requiresConfirmation: true,
|
|
1243
|
+
removals: comparison.removals,
|
|
1244
|
+
error: hasContentReductions ? "Page content reduction detected - confirmation required" : "Confirmation required for critical removals"
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
if (!snapshotId && this.snapshotStore && (removals.critical.length > 0 || removals.pageContentReductions.length > 0)) {
|
|
1248
|
+
snapshotId = await this.snapshotStore.create(
|
|
1249
|
+
uid,
|
|
1250
|
+
appId,
|
|
1251
|
+
existingSchema,
|
|
1252
|
+
`auto_before_removal_${Date.now()}`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const firestoreData = toFirestoreFormat(schema);
|
|
1258
|
+
const now = Date.now();
|
|
1259
|
+
const docData = {
|
|
1260
|
+
...firestoreData,
|
|
1261
|
+
_metadata: {
|
|
1262
|
+
version: options.expectedVersion ? options.expectedVersion + 1 : 1,
|
|
1263
|
+
updatedAt: now,
|
|
1264
|
+
createdAt: existingSchema ? void 0 : now,
|
|
1265
|
+
source: options.source || "manual"
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
const db2 = getFirestore();
|
|
1269
|
+
await db2.doc(`users/${uid}/${this.appsCollection}/${appId}`).set(docData, { merge: true });
|
|
1270
|
+
this.invalidateCache(uid, appId);
|
|
1271
|
+
return { success: true, snapshotId };
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error("[SchemaStore] Error saving schema:", error);
|
|
1274
|
+
return {
|
|
1275
|
+
success: false,
|
|
1276
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
/** Create a new app with initial schema */
|
|
1281
|
+
async create(uid, metadata) {
|
|
1282
|
+
const appId = `app-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1283
|
+
const now = Date.now();
|
|
1284
|
+
const schema = {
|
|
1285
|
+
name: metadata.name,
|
|
1286
|
+
description: metadata.description,
|
|
1287
|
+
orbitals: []
|
|
1288
|
+
};
|
|
1289
|
+
const firestoreData = toFirestoreFormat(schema);
|
|
1290
|
+
const docData = {
|
|
1291
|
+
...firestoreData,
|
|
1292
|
+
_metadata: { version: 1, createdAt: now, updatedAt: now, source: "manual" }
|
|
1293
|
+
};
|
|
1294
|
+
const db2 = getFirestore();
|
|
1295
|
+
await db2.doc(`users/${uid}/${this.appsCollection}/${appId}`).set(docData);
|
|
1296
|
+
this.listCache.delete(uid);
|
|
1297
|
+
return { appId, schema };
|
|
1298
|
+
}
|
|
1299
|
+
/** Delete an app */
|
|
1300
|
+
async delete(uid, appId) {
|
|
1301
|
+
try {
|
|
1302
|
+
const db2 = getFirestore();
|
|
1303
|
+
const ref = db2.doc(`users/${uid}/${this.appsCollection}/${appId}`);
|
|
1304
|
+
const doc = await ref.get();
|
|
1305
|
+
if (!doc.exists) return false;
|
|
1306
|
+
await ref.delete();
|
|
1307
|
+
this.invalidateCache(uid, appId);
|
|
1308
|
+
return true;
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
console.error("[SchemaStore] Error deleting app:", error);
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
/** List all apps for a user */
|
|
1315
|
+
async list(uid) {
|
|
1316
|
+
const cached = this.listCache.get(uid);
|
|
1317
|
+
if (cached && Date.now() - cached.timestamp < LIST_CACHE_TTL_MS) {
|
|
1318
|
+
return cached.apps;
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
const db2 = getFirestore();
|
|
1322
|
+
const snapshot = await db2.collection(`users/${uid}/${this.appsCollection}`).select("name", "description", "domainContext", "_metadata", "orbitalCount", "traitCount").orderBy("_metadata.updatedAt", "desc").get();
|
|
1323
|
+
const apps = snapshot.docs.map((doc) => {
|
|
1324
|
+
const data = doc.data();
|
|
1325
|
+
const metadata = data._metadata;
|
|
1326
|
+
const orbitalCount = data.orbitalCount;
|
|
1327
|
+
return {
|
|
1328
|
+
id: doc.id,
|
|
1329
|
+
name: data.name || "Untitled",
|
|
1330
|
+
description: data.description,
|
|
1331
|
+
updatedAt: metadata?.updatedAt || Date.now(),
|
|
1332
|
+
createdAt: metadata?.createdAt || Date.now(),
|
|
1333
|
+
stats: { entities: orbitalCount ?? 0, pages: 0, states: 0, events: 0, transitions: 0 },
|
|
1334
|
+
domainContext: data.domainContext,
|
|
1335
|
+
hasValidationErrors: false
|
|
1336
|
+
};
|
|
1337
|
+
});
|
|
1338
|
+
this.listCache.set(uid, { apps, timestamp: Date.now() });
|
|
1339
|
+
return apps;
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
console.error("[SchemaStore] Error listing apps:", error);
|
|
1342
|
+
return [];
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/** Compute stats from OrbitalSchema */
|
|
1346
|
+
computeStats(schema) {
|
|
1347
|
+
const orbitals = schema.orbitals || [];
|
|
1348
|
+
const entities = orbitals.length;
|
|
1349
|
+
const pages = orbitals.reduce((n, o) => n + (o.pages?.length || 0), 0);
|
|
1350
|
+
const allTraits = [
|
|
1351
|
+
...schema.traits || [],
|
|
1352
|
+
...orbitals.flatMap(
|
|
1353
|
+
(o) => (o.traits || []).filter((t) => typeof t !== "string" && "stateMachine" in t)
|
|
1354
|
+
)
|
|
1355
|
+
];
|
|
1356
|
+
return {
|
|
1357
|
+
states: allTraits.flatMap((t) => t.stateMachine?.states || []).length,
|
|
1358
|
+
events: allTraits.flatMap((t) => t.stateMachine?.events || []).length,
|
|
1359
|
+
pages,
|
|
1360
|
+
entities,
|
|
1361
|
+
transitions: allTraits.flatMap((t) => t.stateMachine?.transitions || []).length
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
/** Invalidate caches for a specific app */
|
|
1365
|
+
invalidateCache(uid, appId) {
|
|
1366
|
+
this.schemaCache.delete(`${uid}:${appId}`);
|
|
1367
|
+
this.listCache.delete(uid);
|
|
1368
|
+
}
|
|
1369
|
+
/** Clear all caches */
|
|
1370
|
+
clearCaches() {
|
|
1371
|
+
this.schemaCache.clear();
|
|
1372
|
+
this.listCache.clear();
|
|
1373
|
+
}
|
|
1374
|
+
/** Get the collection path for an app */
|
|
1375
|
+
getAppDocPath(uid, appId) {
|
|
1376
|
+
return `users/${uid}/${this.appsCollection}/${appId}`;
|
|
1377
|
+
}
|
|
1378
|
+
/** Expose apps collection name for subcollection stores */
|
|
1379
|
+
getAppsCollection() {
|
|
1380
|
+
return this.appsCollection;
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
// src/stores/SnapshotStore.ts
|
|
1385
|
+
var SNAPSHOTS_COLLECTION = "snapshots";
|
|
1386
|
+
var SnapshotStore = class {
|
|
1387
|
+
appsCollection;
|
|
1388
|
+
constructor(appsCollection = "apps") {
|
|
1389
|
+
this.appsCollection = appsCollection;
|
|
1390
|
+
}
|
|
1391
|
+
getCollectionPath(uid, appId) {
|
|
1392
|
+
return `users/${uid}/${this.appsCollection}/${appId}/${SNAPSHOTS_COLLECTION}`;
|
|
1393
|
+
}
|
|
1394
|
+
getAppDocPath(uid, appId) {
|
|
1395
|
+
return `users/${uid}/${this.appsCollection}/${appId}`;
|
|
1396
|
+
}
|
|
1397
|
+
/** Create a snapshot of the current schema */
|
|
1398
|
+
async create(uid, appId, schema, reason) {
|
|
1399
|
+
const db2 = getFirestore();
|
|
1400
|
+
const snapshotId = `snapshot_${Date.now()}`;
|
|
1401
|
+
const snapshotDoc = {
|
|
1402
|
+
id: snapshotId,
|
|
1403
|
+
timestamp: Date.now(),
|
|
1404
|
+
schema: toFirestoreFormat(schema),
|
|
1405
|
+
reason
|
|
1406
|
+
};
|
|
1407
|
+
await db2.doc(`${this.getCollectionPath(uid, appId)}/${snapshotId}`).set(snapshotDoc);
|
|
1408
|
+
const appDocRef = db2.doc(this.getAppDocPath(uid, appId));
|
|
1409
|
+
const appDoc = await appDocRef.get();
|
|
1410
|
+
const currentMeta = appDoc.data()?._historyMeta;
|
|
1411
|
+
const updatedMeta = {
|
|
1412
|
+
latestSnapshotId: snapshotId,
|
|
1413
|
+
latestChangeSetId: currentMeta?.latestChangeSetId,
|
|
1414
|
+
snapshotCount: (currentMeta?.snapshotCount || 0) + 1,
|
|
1415
|
+
changeSetCount: currentMeta?.changeSetCount || 0
|
|
1416
|
+
};
|
|
1417
|
+
await appDocRef.set({ _historyMeta: updatedMeta }, { merge: true });
|
|
1418
|
+
return snapshotId;
|
|
1419
|
+
}
|
|
1420
|
+
/** Get all snapshots for an app (ordered by timestamp desc) */
|
|
1421
|
+
async getAll(uid, appId) {
|
|
1422
|
+
const db2 = getFirestore();
|
|
1423
|
+
const query = await db2.collection(this.getCollectionPath(uid, appId)).orderBy("timestamp", "desc").get();
|
|
1424
|
+
return query.docs.map((doc) => doc.data());
|
|
1425
|
+
}
|
|
1426
|
+
/** Get a specific snapshot by ID */
|
|
1427
|
+
async get(uid, appId, snapshotId) {
|
|
1428
|
+
const db2 = getFirestore();
|
|
1429
|
+
const doc = await db2.doc(`${this.getCollectionPath(uid, appId)}/${snapshotId}`).get();
|
|
1430
|
+
if (!doc.exists) return null;
|
|
1431
|
+
return doc.data();
|
|
1432
|
+
}
|
|
1433
|
+
/** Delete a snapshot */
|
|
1434
|
+
async delete(uid, appId, snapshotId) {
|
|
1435
|
+
const db2 = getFirestore();
|
|
1436
|
+
const ref = db2.doc(`${this.getCollectionPath(uid, appId)}/${snapshotId}`);
|
|
1437
|
+
const doc = await ref.get();
|
|
1438
|
+
if (!doc.exists) return false;
|
|
1439
|
+
await ref.delete();
|
|
1440
|
+
const appDocRef = db2.doc(this.getAppDocPath(uid, appId));
|
|
1441
|
+
const appDoc = await appDocRef.get();
|
|
1442
|
+
const currentMeta = appDoc.data()?._historyMeta;
|
|
1443
|
+
if (currentMeta) {
|
|
1444
|
+
const updatedMeta = {
|
|
1445
|
+
...currentMeta,
|
|
1446
|
+
snapshotCount: Math.max(0, (currentMeta.snapshotCount || 1) - 1),
|
|
1447
|
+
latestSnapshotId: currentMeta.latestSnapshotId === snapshotId ? void 0 : currentMeta.latestSnapshotId
|
|
1448
|
+
};
|
|
1449
|
+
await appDocRef.set({ _historyMeta: updatedMeta }, { merge: true });
|
|
1450
|
+
}
|
|
1451
|
+
return true;
|
|
1452
|
+
}
|
|
1453
|
+
/** Get schema snapshot at a specific version */
|
|
1454
|
+
async getByVersion(uid, appId, version) {
|
|
1455
|
+
const db2 = getFirestore();
|
|
1456
|
+
const query = await db2.collection(this.getCollectionPath(uid, appId)).where("version", "==", version).limit(1).get();
|
|
1457
|
+
if (query.empty) return null;
|
|
1458
|
+
const snapshot = query.docs[0].data();
|
|
1459
|
+
return fromFirestoreFormat(snapshot.schema);
|
|
1460
|
+
}
|
|
1461
|
+
/** Get the schema from a snapshot (deserialized) */
|
|
1462
|
+
getSchemaFromSnapshot(snapshot) {
|
|
1463
|
+
return fromFirestoreFormat(snapshot.schema);
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
// src/stores/ChangeSetStore.ts
|
|
1468
|
+
var CHANGESETS_COLLECTION = "changesets";
|
|
1469
|
+
var ChangeSetStore = class {
|
|
1470
|
+
appsCollection;
|
|
1471
|
+
constructor(appsCollection = "apps") {
|
|
1472
|
+
this.appsCollection = appsCollection;
|
|
1473
|
+
}
|
|
1474
|
+
getCollectionPath(uid, appId) {
|
|
1475
|
+
return `users/${uid}/${this.appsCollection}/${appId}/${CHANGESETS_COLLECTION}`;
|
|
1476
|
+
}
|
|
1477
|
+
getAppDocPath(uid, appId) {
|
|
1478
|
+
return `users/${uid}/${this.appsCollection}/${appId}`;
|
|
1479
|
+
}
|
|
1480
|
+
/** Append a changeset to history */
|
|
1481
|
+
async append(uid, appId, changeSet) {
|
|
1482
|
+
const db2 = getFirestore();
|
|
1483
|
+
await db2.doc(`${this.getCollectionPath(uid, appId)}/${changeSet.id}`).set(changeSet);
|
|
1484
|
+
const appDocRef = db2.doc(this.getAppDocPath(uid, appId));
|
|
1485
|
+
const appDoc = await appDocRef.get();
|
|
1486
|
+
const currentMeta = appDoc.data()?._historyMeta;
|
|
1487
|
+
const updatedMeta = {
|
|
1488
|
+
latestSnapshotId: currentMeta?.latestSnapshotId,
|
|
1489
|
+
latestChangeSetId: changeSet.id,
|
|
1490
|
+
snapshotCount: currentMeta?.snapshotCount || 0,
|
|
1491
|
+
changeSetCount: (currentMeta?.changeSetCount || 0) + 1
|
|
1492
|
+
};
|
|
1493
|
+
await appDocRef.set({ _historyMeta: updatedMeta }, { merge: true });
|
|
1494
|
+
}
|
|
1495
|
+
/** Get change history for an app (ordered by version desc) */
|
|
1496
|
+
async getHistory(uid, appId) {
|
|
1497
|
+
try {
|
|
1498
|
+
const db2 = getFirestore();
|
|
1499
|
+
const query = await db2.collection(this.getCollectionPath(uid, appId)).orderBy("version", "desc").get();
|
|
1500
|
+
return query.docs.map((doc) => doc.data());
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
console.error("[ChangeSetStore] Error getting change history:", error);
|
|
1503
|
+
return [];
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
/** Get a specific changeset by ID */
|
|
1507
|
+
async get(uid, appId, changeSetId) {
|
|
1508
|
+
const db2 = getFirestore();
|
|
1509
|
+
const doc = await db2.doc(`${this.getCollectionPath(uid, appId)}/${changeSetId}`).get();
|
|
1510
|
+
if (!doc.exists) return null;
|
|
1511
|
+
return doc.data();
|
|
1512
|
+
}
|
|
1513
|
+
/** Update a changeset's status */
|
|
1514
|
+
async updateStatus(uid, appId, changeSetId, status) {
|
|
1515
|
+
const db2 = getFirestore();
|
|
1516
|
+
const ref = db2.doc(`${this.getCollectionPath(uid, appId)}/${changeSetId}`);
|
|
1517
|
+
const doc = await ref.get();
|
|
1518
|
+
if (!doc.exists) return;
|
|
1519
|
+
await ref.update({ status });
|
|
1520
|
+
}
|
|
1521
|
+
/** Delete a changeset */
|
|
1522
|
+
async delete(uid, appId, changeSetId) {
|
|
1523
|
+
const db2 = getFirestore();
|
|
1524
|
+
const ref = db2.doc(`${this.getCollectionPath(uid, appId)}/${changeSetId}`);
|
|
1525
|
+
const doc = await ref.get();
|
|
1526
|
+
if (!doc.exists) return false;
|
|
1527
|
+
await ref.delete();
|
|
1528
|
+
const appDocRef = db2.doc(this.getAppDocPath(uid, appId));
|
|
1529
|
+
const appDoc = await appDocRef.get();
|
|
1530
|
+
const currentMeta = appDoc.data()?._historyMeta;
|
|
1531
|
+
if (currentMeta) {
|
|
1532
|
+
const updatedMeta = {
|
|
1533
|
+
...currentMeta,
|
|
1534
|
+
changeSetCount: Math.max(0, (currentMeta.changeSetCount || 1) - 1),
|
|
1535
|
+
latestChangeSetId: currentMeta.latestChangeSetId === changeSetId ? void 0 : currentMeta.latestChangeSetId
|
|
1536
|
+
};
|
|
1537
|
+
await appDocRef.set({ _historyMeta: updatedMeta }, { merge: true });
|
|
1538
|
+
}
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
// src/stores/ValidationStore.ts
|
|
1544
|
+
var VALIDATION_COLLECTION = "validation";
|
|
1545
|
+
var VALIDATION_DOC_ID = "current";
|
|
1546
|
+
var ValidationStore = class {
|
|
1547
|
+
appsCollection;
|
|
1548
|
+
constructor(appsCollection = "apps") {
|
|
1549
|
+
this.appsCollection = appsCollection;
|
|
1550
|
+
}
|
|
1551
|
+
getDocPath(uid, appId) {
|
|
1552
|
+
return `users/${uid}/${this.appsCollection}/${appId}/${VALIDATION_COLLECTION}/${VALIDATION_DOC_ID}`;
|
|
1553
|
+
}
|
|
1554
|
+
getAppDocPath(uid, appId) {
|
|
1555
|
+
return `users/${uid}/${this.appsCollection}/${appId}`;
|
|
1556
|
+
}
|
|
1557
|
+
/** Save validation results */
|
|
1558
|
+
async save(uid, appId, results) {
|
|
1559
|
+
const db2 = getFirestore();
|
|
1560
|
+
await db2.doc(this.getDocPath(uid, appId)).set(results);
|
|
1561
|
+
const validationMeta = {
|
|
1562
|
+
errorCount: results.errors?.length || 0,
|
|
1563
|
+
warningCount: results.warnings?.length || 0,
|
|
1564
|
+
validatedAt: results.validatedAt
|
|
1565
|
+
};
|
|
1566
|
+
await db2.doc(this.getAppDocPath(uid, appId)).set(
|
|
1567
|
+
{ _operational: { validationMeta } },
|
|
1568
|
+
{ merge: true }
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
/** Get validation results */
|
|
1572
|
+
async get(uid, appId) {
|
|
1573
|
+
try {
|
|
1574
|
+
const db2 = getFirestore();
|
|
1575
|
+
const doc = await db2.doc(this.getDocPath(uid, appId)).get();
|
|
1576
|
+
if (!doc.exists) return null;
|
|
1577
|
+
return doc.data();
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
console.error("[ValidationStore] Error getting validation results:", error);
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/** Clear validation results */
|
|
1584
|
+
async clear(uid, appId) {
|
|
1585
|
+
const db2 = getFirestore();
|
|
1586
|
+
const { FieldValue } = await import('firebase-admin/firestore');
|
|
1587
|
+
await db2.doc(this.getDocPath(uid, appId)).delete();
|
|
1588
|
+
await db2.doc(this.getAppDocPath(uid, appId)).update({
|
|
1589
|
+
"_operational.validationMeta": FieldValue.delete()
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
export { AppError, ChangeSetStore, ConflictError, EventBus, ForbiddenError, MockDataService, NotFoundError, SchemaProtectionService, SchemaStore, SnapshotStore, UnauthorizedError, ValidationError, ValidationStore, applyFiltersToQuery, asyncHandler, authenticateFirebase, closeWebSocketServer, dataService, db, debugEventsRouter, emitEntityEvent, env, errorHandler, extractPaginationParams, fromFirestoreFormat, getAuth, getConnectedClientCount, getFirestore, getWebSocketServer, logger, mockDataService, notFoundHandler, parseQueryFilters, seedMockData, serverEventBus, setupEventBroadcast, toFirestoreFormat, validateBody, validateParams, validateQuery };
|
|
1014
1595
|
//# sourceMappingURL=index.js.map
|
|
1015
1596
|
//# sourceMappingURL=index.js.map
|