@almadar/server 2.0.3 → 2.0.5

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 CHANGED
@@ -3,8 +3,8 @@ import dotenv from 'dotenv';
3
3
  import { Router } from 'express';
4
4
  import admin from 'firebase-admin';
5
5
  export { default as admin } from 'firebase-admin';
6
- import { WebSocketServer, WebSocket } from 'ws';
7
6
  import { faker } from '@faker-js/faker';
7
+ import { WebSocketServer, WebSocket } from 'ws';
8
8
  import { diffSchemas, categorizeRemovals, detectPageContentReduction, isDestructiveChange, hasSignificantPageReduction, requiresConfirmation } from '@almadar/core';
9
9
  import { getObservabilityCollector, MemoryManager, SessionManager, getMultiUserManager, createWorkflowToolWrapper, createSkillAgent, createUserContext, getStateSyncManager } from '@almadar/agent';
10
10
 
@@ -452,27 +452,6 @@ var EventPersistence = class {
452
452
  return this.store;
453
453
  }
454
454
  };
455
- function debugEventsRouter() {
456
- const router2 = Router();
457
- if (process.env.NODE_ENV !== "development") {
458
- return router2;
459
- }
460
- router2.get("/event-log", (_req, res) => {
461
- const limit = parseInt(String(_req.query.limit) || "50", 10);
462
- const events = getServerEventBus().getRecentEvents(limit);
463
- res.json({ count: events.length, events });
464
- });
465
- router2.delete("/event-log", (_req, res) => {
466
- getServerEventBus().clearEventLog();
467
- res.json({ cleared: true });
468
- });
469
- router2.get("/listeners", (_req, res) => {
470
- const counts = getServerEventBus().getListenerCounts();
471
- const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
472
- res.json({ total, events: counts });
473
- });
474
- return router2;
475
- }
476
455
  function initializeFirebase() {
477
456
  if (admin.apps.length > 0) {
478
457
  return admin.app();
@@ -537,380 +516,117 @@ var db = new Proxy({}, {
537
516
  return typeof value === "function" ? value.bind(firestore) : value;
538
517
  }
539
518
  });
540
- var wss = null;
541
- function setupEventBroadcast(server, path = "/ws/events") {
542
- if (wss) {
543
- logger.warn("[WebSocket] Server already initialized");
544
- return wss;
545
- }
546
- wss = new WebSocketServer({ server, path });
547
- logger.info(`[WebSocket] Server listening at ${path}`);
548
- wss.on("connection", (ws, req) => {
549
- const clientId = req.headers["sec-websocket-key"] || "unknown";
550
- logger.debug(`[WebSocket] Client connected: ${clientId}`);
551
- ws.send(
552
- JSON.stringify({
553
- type: "CONNECTED",
554
- timestamp: Date.now(),
555
- message: "Connected to event stream"
556
- })
557
- );
558
- ws.on("message", (data) => {
559
- try {
560
- const message = JSON.parse(data.toString());
561
- logger.debug(`[WebSocket] Received from ${clientId}:`, message);
562
- if (message.type && message.payload) {
563
- getServerEventBus().emit(message.type, message.payload, {
564
- orbital: "client",
565
- entity: clientId
566
- });
567
- }
568
- } catch (error) {
569
- logger.error(`[WebSocket] Failed to parse message:`, error);
570
- }
571
- });
572
- ws.on("close", () => {
573
- logger.debug(`[WebSocket] Client disconnected: ${clientId}`);
574
- });
575
- ws.on("error", (error) => {
576
- logger.error(`[WebSocket] Client error:`, error);
577
- });
578
- });
579
- getServerEventBus().on("*", (event) => {
580
- if (!wss) return;
581
- const typedEvent = event;
582
- const message = JSON.stringify({
583
- type: typedEvent.type,
584
- payload: typedEvent.payload,
585
- timestamp: typedEvent.timestamp,
586
- source: typedEvent.source
587
- });
588
- let broadcastCount = 0;
589
- wss.clients.forEach((client) => {
590
- if (client.readyState === WebSocket.OPEN) {
591
- client.send(message);
592
- broadcastCount++;
593
- }
594
- });
595
- if (broadcastCount > 0) {
596
- logger.debug(`[WebSocket] Broadcast ${typedEvent.type} to ${broadcastCount} client(s)`);
597
- }
598
- });
599
- return wss;
600
- }
601
- function getWebSocketServer() {
602
- return wss;
603
- }
604
- function closeWebSocketServer() {
605
- return new Promise((resolve, reject) => {
606
- if (!wss) {
607
- resolve();
608
- return;
519
+ var MockDataService = class {
520
+ stores = /* @__PURE__ */ new Map();
521
+ schemas = /* @__PURE__ */ new Map();
522
+ idCounters = /* @__PURE__ */ new Map();
523
+ constructor() {
524
+ if (env.MOCK_SEED !== void 0) {
525
+ faker.seed(env.MOCK_SEED);
526
+ logger.info(`[Mock] Using seed: ${env.MOCK_SEED}`);
609
527
  }
610
- wss.close((err) => {
611
- if (err) {
612
- reject(err);
613
- } else {
614
- wss = null;
615
- resolve();
616
- }
617
- });
618
- });
619
- }
620
- function getConnectedClientCount() {
621
- if (!wss) return 0;
622
- return wss.clients.size;
623
- }
624
- var AppError = class extends Error {
625
- constructor(statusCode, message, code) {
626
- super(message);
627
- this.statusCode = statusCode;
628
- this.message = message;
629
- this.code = code;
630
- this.name = "AppError";
631
528
  }
632
- };
633
- var NotFoundError = class extends AppError {
634
- constructor(message = "Resource not found") {
635
- super(404, message, "NOT_FOUND");
529
+ // ============================================================================
530
+ // Store Management
531
+ // ============================================================================
532
+ /**
533
+ * Initialize store for an entity.
534
+ */
535
+ getStore(entityName) {
536
+ const normalized = entityName.toLowerCase();
537
+ if (!this.stores.has(normalized)) {
538
+ this.stores.set(normalized, /* @__PURE__ */ new Map());
539
+ this.idCounters.set(normalized, 0);
540
+ }
541
+ return this.stores.get(normalized);
636
542
  }
637
- };
638
- var ValidationError = class extends AppError {
639
- constructor(message = "Validation failed") {
640
- super(400, message, "VALIDATION_ERROR");
543
+ /**
544
+ * Generate next ID for an entity.
545
+ */
546
+ nextId(entityName) {
547
+ const normalized = entityName.toLowerCase();
548
+ const counter = (this.idCounters.get(normalized) ?? 0) + 1;
549
+ this.idCounters.set(normalized, counter);
550
+ return `mock-${normalized}-${counter}`;
641
551
  }
642
- };
643
- var UnauthorizedError = class extends AppError {
644
- constructor(message = "Unauthorized") {
645
- super(401, message, "UNAUTHORIZED");
552
+ // ============================================================================
553
+ // Schema & Seeding
554
+ // ============================================================================
555
+ /**
556
+ * Register an entity schema.
557
+ */
558
+ registerSchema(entityName, schema) {
559
+ this.schemas.set(entityName.toLowerCase(), schema);
646
560
  }
647
- };
648
- var ForbiddenError = class extends AppError {
649
- constructor(message = "Forbidden") {
650
- super(403, message, "FORBIDDEN");
561
+ /**
562
+ * Seed an entity with mock data.
563
+ */
564
+ seed(entityName, fields, count = 10) {
565
+ const store = this.getStore(entityName);
566
+ const normalized = entityName.toLowerCase();
567
+ logger.info(`[Mock] Seeding ${count} ${entityName}...`);
568
+ for (let i = 0; i < count; i++) {
569
+ const item = this.generateMockItem(normalized, fields, i + 1);
570
+ store.set(item.id, item);
571
+ }
651
572
  }
652
- };
653
- var ConflictError = class extends AppError {
654
- constructor(message = "Resource conflict") {
655
- super(409, message, "CONFLICT");
573
+ /**
574
+ * Generate a single mock item based on field schemas.
575
+ */
576
+ generateMockItem(entityName, fields, index) {
577
+ const id = this.nextId(entityName);
578
+ const now = /* @__PURE__ */ new Date();
579
+ const item = {
580
+ id,
581
+ createdAt: faker.date.past({ years: 1 }),
582
+ updatedAt: now
583
+ };
584
+ for (const field of fields) {
585
+ if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
586
+ continue;
587
+ }
588
+ item[field.name] = this.generateFieldValue(entityName, field, index);
589
+ }
590
+ return item;
656
591
  }
657
- };
658
- var errorHandler = (err, _req, res, _next) => {
659
- logger.error("Error:", { name: err.name, message: err.message, stack: err.stack });
660
- if (err instanceof ZodError) {
661
- res.status(400).json({
662
- success: false,
663
- error: "Validation failed",
664
- code: "VALIDATION_ERROR",
665
- details: err.errors.map((e) => ({
666
- path: e.path.join("."),
667
- message: e.message
668
- }))
669
- });
670
- return;
671
- }
672
- if (err instanceof AppError) {
673
- res.status(err.statusCode).json({
674
- success: false,
675
- error: err.message,
676
- code: err.code
677
- });
678
- return;
679
- }
680
- if (err.name === "FirebaseError" || err.name === "FirestoreError") {
681
- res.status(500).json({
682
- success: false,
683
- error: "Database error",
684
- code: "DATABASE_ERROR"
685
- });
686
- return;
687
- }
688
- res.status(500).json({
689
- success: false,
690
- error: "Internal server error",
691
- code: "INTERNAL_ERROR"
692
- });
693
- };
694
- var asyncHandler = (fn) => (req, res, next) => {
695
- Promise.resolve(fn(req, res, next)).catch(next);
696
- };
697
- var notFoundHandler = (req, res) => {
698
- res.status(404).json({
699
- success: false,
700
- error: `Route ${req.method} ${req.path} not found`,
701
- code: "ROUTE_NOT_FOUND"
702
- });
703
- };
704
- var validateBody = (schema) => async (req, res, next) => {
705
- try {
706
- req.body = await schema.parseAsync(req.body);
707
- next();
708
- } catch (error) {
709
- if (error instanceof ZodError) {
710
- res.status(400).json({
711
- success: false,
712
- error: "Validation failed",
713
- code: "VALIDATION_ERROR",
714
- details: error.errors.map((e) => ({
715
- path: e.path.join("."),
716
- message: e.message
717
- }))
718
- });
719
- return;
720
- }
721
- next(error);
722
- }
723
- };
724
- var validateQuery = (schema) => async (req, res, next) => {
725
- try {
726
- req.query = await schema.parseAsync(req.query);
727
- next();
728
- } catch (error) {
729
- if (error instanceof ZodError) {
730
- res.status(400).json({
731
- success: false,
732
- error: "Invalid query parameters",
733
- code: "VALIDATION_ERROR",
734
- details: error.errors.map((e) => ({
735
- path: e.path.join("."),
736
- message: e.message
737
- }))
738
- });
739
- return;
740
- }
741
- next(error);
742
- }
743
- };
744
- var validateParams = (schema) => async (req, res, next) => {
745
- try {
746
- req.params = await schema.parseAsync(req.params);
747
- next();
748
- } catch (error) {
749
- if (error instanceof ZodError) {
750
- res.status(400).json({
751
- success: false,
752
- error: "Invalid path parameters",
753
- code: "VALIDATION_ERROR",
754
- details: error.errors.map((e) => ({
755
- path: e.path.join("."),
756
- message: e.message
757
- }))
758
- });
759
- return;
760
- }
761
- next(error);
762
- }
763
- };
764
-
765
- // src/middleware/authenticateFirebase.ts
766
- var BEARER_PREFIX = "Bearer ";
767
- var DEV_USER = {
768
- uid: "dev-user-001",
769
- email: "dev@localhost",
770
- email_verified: true,
771
- aud: "dev-project",
772
- auth_time: Math.floor(Date.now() / 1e3),
773
- exp: Math.floor(Date.now() / 1e3) + 3600,
774
- iat: Math.floor(Date.now() / 1e3),
775
- iss: "https://securetoken.google.com/dev-project",
776
- sub: "dev-user-001",
777
- firebase: {
778
- identities: {},
779
- sign_in_provider: "custom"
780
- }
781
- };
782
- async function authenticateFirebase(req, res, next) {
783
- const authorization = req.headers.authorization;
784
- if (env.NODE_ENV === "development" && (!authorization || !authorization.startsWith(BEARER_PREFIX))) {
785
- req.firebaseUser = DEV_USER;
786
- res.locals.firebaseUser = DEV_USER;
787
- return next();
788
- }
789
- try {
790
- if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
791
- return res.status(401).json({ error: "Authorization header missing or malformed" });
792
- }
793
- const token = authorization.slice(BEARER_PREFIX.length);
794
- const decodedToken = await getAuth().verifyIdToken(token);
795
- req.firebaseUser = decodedToken;
796
- res.locals.firebaseUser = decodedToken;
797
- return next();
798
- } catch (error) {
799
- console.error("Firebase authentication failed:", error);
800
- return res.status(401).json({ error: "Unauthorized" });
801
- }
802
- }
803
- var MockDataService = class {
804
- stores = /* @__PURE__ */ new Map();
805
- schemas = /* @__PURE__ */ new Map();
806
- idCounters = /* @__PURE__ */ new Map();
807
- constructor() {
808
- if (env.MOCK_SEED !== void 0) {
809
- faker.seed(env.MOCK_SEED);
810
- logger.info(`[Mock] Using seed: ${env.MOCK_SEED}`);
811
- }
812
- }
813
- // ============================================================================
814
- // Store Management
815
- // ============================================================================
816
- /**
817
- * Initialize store for an entity.
818
- */
819
- getStore(entityName) {
820
- const normalized = entityName.toLowerCase();
821
- if (!this.stores.has(normalized)) {
822
- this.stores.set(normalized, /* @__PURE__ */ new Map());
823
- this.idCounters.set(normalized, 0);
824
- }
825
- return this.stores.get(normalized);
826
- }
827
- /**
828
- * Generate next ID for an entity.
829
- */
830
- nextId(entityName) {
831
- const normalized = entityName.toLowerCase();
832
- const counter = (this.idCounters.get(normalized) ?? 0) + 1;
833
- this.idCounters.set(normalized, counter);
834
- return `mock-${normalized}-${counter}`;
835
- }
836
- // ============================================================================
837
- // Schema & Seeding
838
- // ============================================================================
839
- /**
840
- * Register an entity schema.
841
- */
842
- registerSchema(entityName, schema) {
843
- this.schemas.set(entityName.toLowerCase(), schema);
844
- }
845
- /**
846
- * Seed an entity with mock data.
847
- */
848
- seed(entityName, fields, count = 10) {
849
- const store = this.getStore(entityName);
850
- const normalized = entityName.toLowerCase();
851
- logger.info(`[Mock] Seeding ${count} ${entityName}...`);
852
- for (let i = 0; i < count; i++) {
853
- const item = this.generateMockItem(normalized, fields, i + 1);
854
- store.set(item.id, item);
855
- }
856
- }
857
- /**
858
- * Generate a single mock item based on field schemas.
859
- */
860
- generateMockItem(entityName, fields, index) {
861
- const id = this.nextId(entityName);
862
- const now = /* @__PURE__ */ new Date();
863
- const item = {
864
- id,
865
- createdAt: faker.date.past({ years: 1 }),
866
- updatedAt: now
867
- };
868
- for (const field of fields) {
869
- if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
870
- continue;
871
- }
872
- item[field.name] = this.generateFieldValue(entityName, field, index);
873
- }
874
- return item;
875
- }
876
- /**
877
- * Generate a mock value for a field based on its schema.
878
- */
879
- generateFieldValue(entityName, field, index) {
880
- if (!field.required && Math.random() > 0.8) {
881
- return void 0;
882
- }
883
- switch (field.type) {
884
- case "string":
885
- return this.generateStringValue(entityName, field, index);
886
- case "number":
887
- return faker.number.int({
888
- min: field.min ?? 0,
889
- max: field.max ?? 1e3
890
- });
891
- case "boolean":
892
- return faker.datatype.boolean();
893
- case "date":
894
- return this.generateDateValue(field);
895
- case "enum":
896
- if (field.enumValues && field.enumValues.length > 0) {
897
- return faker.helpers.arrayElement(field.enumValues);
898
- }
899
- return null;
900
- case "relation":
901
- if (field.relatedEntity) {
902
- const relatedStore = this.stores.get(field.relatedEntity.toLowerCase());
903
- if (relatedStore && relatedStore.size > 0) {
904
- const ids = Array.from(relatedStore.keys());
905
- return faker.helpers.arrayElement(ids);
906
- }
907
- }
908
- return null;
909
- case "array":
910
- return [];
911
- default:
912
- return null;
913
- }
592
+ /**
593
+ * Generate a mock value for a field based on its schema.
594
+ */
595
+ generateFieldValue(entityName, field, index) {
596
+ if (!field.required && Math.random() > 0.8) {
597
+ return void 0;
598
+ }
599
+ switch (field.type) {
600
+ case "string":
601
+ return this.generateStringValue(entityName, field, index);
602
+ case "number":
603
+ return faker.number.int({
604
+ min: field.min ?? 0,
605
+ max: field.max ?? 1e3
606
+ });
607
+ case "boolean":
608
+ return faker.datatype.boolean();
609
+ case "date":
610
+ return this.generateDateValue(field);
611
+ case "enum":
612
+ if (field.enumValues && field.enumValues.length > 0) {
613
+ return faker.helpers.arrayElement(field.enumValues);
614
+ }
615
+ return null;
616
+ case "relation":
617
+ if (field.relatedEntity) {
618
+ const relatedStore = this.stores.get(field.relatedEntity.toLowerCase());
619
+ if (relatedStore && relatedStore.size > 0) {
620
+ const ids = Array.from(relatedStore.keys());
621
+ return faker.helpers.arrayElement(ids);
622
+ }
623
+ }
624
+ return null;
625
+ case "array":
626
+ return [];
627
+ default:
628
+ return null;
629
+ }
914
630
  }
915
631
  /**
916
632
  * Generate a string value based on field name heuristics.
@@ -1349,128 +1065,429 @@ var FirebaseDataService = class {
1349
1065
  const data = items.slice(startIndex, startIndex + pageSize);
1350
1066
  return { data, total, page, pageSize, totalPages };
1351
1067
  }
1352
- async getById(collection, id) {
1353
- const doc = await db.collection(collection).doc(id).get();
1354
- if (!doc.exists) {
1355
- return null;
1356
- }
1357
- return { id: doc.id, ...doc.data() };
1068
+ async getById(collection, id) {
1069
+ const doc = await db.collection(collection).doc(id).get();
1070
+ if (!doc.exists) {
1071
+ return null;
1072
+ }
1073
+ return { id: doc.id, ...doc.data() };
1074
+ }
1075
+ async create(collection, data) {
1076
+ const now = /* @__PURE__ */ new Date();
1077
+ const docRef = await db.collection(collection).add({
1078
+ ...data,
1079
+ createdAt: now,
1080
+ updatedAt: now
1081
+ });
1082
+ return {
1083
+ ...data,
1084
+ id: docRef.id,
1085
+ createdAt: now,
1086
+ updatedAt: now
1087
+ };
1088
+ }
1089
+ async update(collection, id, data) {
1090
+ const docRef = db.collection(collection).doc(id);
1091
+ const doc = await docRef.get();
1092
+ if (!doc.exists) {
1093
+ return null;
1094
+ }
1095
+ const now = /* @__PURE__ */ new Date();
1096
+ await docRef.update({
1097
+ ...data,
1098
+ updatedAt: now
1099
+ });
1100
+ return {
1101
+ ...doc.data(),
1102
+ ...data,
1103
+ id,
1104
+ updatedAt: now
1105
+ };
1106
+ }
1107
+ async delete(collection, id) {
1108
+ const docRef = db.collection(collection).doc(id);
1109
+ const doc = await docRef.get();
1110
+ if (!doc.exists) {
1111
+ return false;
1112
+ }
1113
+ await docRef.delete();
1114
+ return true;
1115
+ }
1116
+ async query(collection, filters) {
1117
+ let query = db.collection(collection);
1118
+ const memoryFilters = [];
1119
+ for (const filter of filters) {
1120
+ if (["==", "!=", "<", "<=", ">", ">=", "in", "not-in"].includes(filter.op)) {
1121
+ query = query.where(filter.field, filter.op, filter.value);
1122
+ } else {
1123
+ memoryFilters.push(filter);
1124
+ }
1125
+ }
1126
+ const snapshot = await query.get();
1127
+ let items = snapshot.docs.map((doc) => ({
1128
+ id: doc.id,
1129
+ ...doc.data()
1130
+ }));
1131
+ for (const filter of memoryFilters) {
1132
+ items = items.filter((item) => {
1133
+ const value = item[filter.field];
1134
+ return applyFilterCondition(value, filter.op, filter.value);
1135
+ });
1136
+ }
1137
+ return items;
1138
+ }
1139
+ getStore(collection) {
1140
+ const svc = this;
1141
+ return {
1142
+ async getById(id) {
1143
+ return svc.getById(collection, id);
1144
+ },
1145
+ async create(data) {
1146
+ return svc.create(collection, data);
1147
+ },
1148
+ async update(id, data) {
1149
+ const result = await svc.update(collection, id, data);
1150
+ if (!result) throw new Error(`Entity ${id} not found in ${collection}`);
1151
+ return result;
1152
+ },
1153
+ async delete(id) {
1154
+ await svc.delete(collection, id);
1155
+ },
1156
+ async query(filters) {
1157
+ return svc.query(collection, filters);
1158
+ }
1159
+ };
1160
+ }
1161
+ };
1162
+ function createDataService() {
1163
+ if (env.USE_MOCK_DATA) {
1164
+ logger.info("[DataService] Using MockDataService");
1165
+ return new MockDataServiceAdapter();
1166
+ }
1167
+ logger.info("[DataService] Using FirebaseDataService");
1168
+ return new FirebaseDataService();
1169
+ }
1170
+ var _dataService = null;
1171
+ function getDataService() {
1172
+ if (!_dataService) {
1173
+ _dataService = createDataService();
1174
+ }
1175
+ return _dataService;
1176
+ }
1177
+ function resetDataService() {
1178
+ _dataService = null;
1179
+ }
1180
+ function seedMockData(entities) {
1181
+ if (!env.USE_MOCK_DATA) {
1182
+ logger.info("[DataService] Mock mode disabled, skipping seed");
1183
+ return;
1184
+ }
1185
+ logger.info("[DataService] Seeding mock data...");
1186
+ for (const entity of entities) {
1187
+ getMockDataService().seed(entity.name, entity.fields, entity.seedCount);
1188
+ }
1189
+ logger.info("[DataService] Mock data seeding complete");
1190
+ }
1191
+
1192
+ // src/lib/debugRouter.ts
1193
+ function debugEventsRouter() {
1194
+ const router2 = Router();
1195
+ if (process.env.NODE_ENV !== "development") {
1196
+ return router2;
1197
+ }
1198
+ router2.get("/event-log", (_req, res) => {
1199
+ const limit = parseInt(String(_req.query.limit) || "50", 10);
1200
+ const events = getServerEventBus().getRecentEvents(limit);
1201
+ res.json({ count: events.length, events });
1202
+ });
1203
+ router2.delete("/event-log", (_req, res) => {
1204
+ getServerEventBus().clearEventLog();
1205
+ res.json({ cleared: true });
1206
+ });
1207
+ router2.get("/listeners", (_req, res) => {
1208
+ const counts = getServerEventBus().getListenerCounts();
1209
+ const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
1210
+ res.json({ total, events: counts });
1211
+ });
1212
+ router2.post("/seed", (req, res) => {
1213
+ const { entities } = req.body;
1214
+ if (!entities || !Array.isArray(entities)) {
1215
+ res.status(400).json({ error: 'Body must have "entities" array' });
1216
+ return;
1217
+ }
1218
+ const configs = entities.map((e) => ({
1219
+ name: e.name,
1220
+ fields: e.fields,
1221
+ seedCount: e.seedCount ?? 5
1222
+ }));
1223
+ seedMockData(configs);
1224
+ const summary = configs.map((c) => `${c.name}(${c.seedCount})`).join(", ");
1225
+ res.json({ seeded: true, summary });
1226
+ });
1227
+ return router2;
1228
+ }
1229
+ var wss = null;
1230
+ function setupEventBroadcast(server, path = "/ws/events") {
1231
+ if (wss) {
1232
+ logger.warn("[WebSocket] Server already initialized");
1233
+ return wss;
1234
+ }
1235
+ wss = new WebSocketServer({ server, path });
1236
+ logger.info(`[WebSocket] Server listening at ${path}`);
1237
+ wss.on("connection", (ws, req) => {
1238
+ const clientId = req.headers["sec-websocket-key"] || "unknown";
1239
+ logger.debug(`[WebSocket] Client connected: ${clientId}`);
1240
+ ws.send(
1241
+ JSON.stringify({
1242
+ type: "CONNECTED",
1243
+ timestamp: Date.now(),
1244
+ message: "Connected to event stream"
1245
+ })
1246
+ );
1247
+ ws.on("message", (data) => {
1248
+ try {
1249
+ const message = JSON.parse(data.toString());
1250
+ logger.debug(`[WebSocket] Received from ${clientId}:`, message);
1251
+ if (message.type && message.payload) {
1252
+ getServerEventBus().emit(message.type, message.payload, {
1253
+ orbital: "client",
1254
+ entity: clientId
1255
+ });
1256
+ }
1257
+ } catch (error) {
1258
+ logger.error(`[WebSocket] Failed to parse message:`, error);
1259
+ }
1260
+ });
1261
+ ws.on("close", () => {
1262
+ logger.debug(`[WebSocket] Client disconnected: ${clientId}`);
1263
+ });
1264
+ ws.on("error", (error) => {
1265
+ logger.error(`[WebSocket] Client error:`, error);
1266
+ });
1267
+ });
1268
+ getServerEventBus().on("*", (event) => {
1269
+ if (!wss) return;
1270
+ const typedEvent = event;
1271
+ const message = JSON.stringify({
1272
+ type: typedEvent.type,
1273
+ payload: typedEvent.payload,
1274
+ timestamp: typedEvent.timestamp,
1275
+ source: typedEvent.source
1276
+ });
1277
+ let broadcastCount = 0;
1278
+ wss.clients.forEach((client) => {
1279
+ if (client.readyState === WebSocket.OPEN) {
1280
+ client.send(message);
1281
+ broadcastCount++;
1282
+ }
1283
+ });
1284
+ if (broadcastCount > 0) {
1285
+ logger.debug(`[WebSocket] Broadcast ${typedEvent.type} to ${broadcastCount} client(s)`);
1286
+ }
1287
+ });
1288
+ return wss;
1289
+ }
1290
+ function getWebSocketServer() {
1291
+ return wss;
1292
+ }
1293
+ function closeWebSocketServer() {
1294
+ return new Promise((resolve, reject) => {
1295
+ if (!wss) {
1296
+ resolve();
1297
+ return;
1298
+ }
1299
+ wss.close((err) => {
1300
+ if (err) {
1301
+ reject(err);
1302
+ } else {
1303
+ wss = null;
1304
+ resolve();
1305
+ }
1306
+ });
1307
+ });
1308
+ }
1309
+ function getConnectedClientCount() {
1310
+ if (!wss) return 0;
1311
+ return wss.clients.size;
1312
+ }
1313
+ var AppError = class extends Error {
1314
+ constructor(statusCode, message, code) {
1315
+ super(message);
1316
+ this.statusCode = statusCode;
1317
+ this.message = message;
1318
+ this.code = code;
1319
+ this.name = "AppError";
1320
+ }
1321
+ };
1322
+ var NotFoundError = class extends AppError {
1323
+ constructor(message = "Resource not found") {
1324
+ super(404, message, "NOT_FOUND");
1325
+ }
1326
+ };
1327
+ var ValidationError = class extends AppError {
1328
+ constructor(message = "Validation failed") {
1329
+ super(400, message, "VALIDATION_ERROR");
1330
+ }
1331
+ };
1332
+ var UnauthorizedError = class extends AppError {
1333
+ constructor(message = "Unauthorized") {
1334
+ super(401, message, "UNAUTHORIZED");
1335
+ }
1336
+ };
1337
+ var ForbiddenError = class extends AppError {
1338
+ constructor(message = "Forbidden") {
1339
+ super(403, message, "FORBIDDEN");
1340
+ }
1341
+ };
1342
+ var ConflictError = class extends AppError {
1343
+ constructor(message = "Resource conflict") {
1344
+ super(409, message, "CONFLICT");
1358
1345
  }
1359
- async create(collection, data) {
1360
- const now = /* @__PURE__ */ new Date();
1361
- const docRef = await db.collection(collection).add({
1362
- ...data,
1363
- createdAt: now,
1364
- updatedAt: now
1346
+ };
1347
+ var errorHandler = (err, _req, res, _next) => {
1348
+ logger.error("Error:", { name: err.name, message: err.message, stack: err.stack });
1349
+ if (err instanceof ZodError) {
1350
+ res.status(400).json({
1351
+ success: false,
1352
+ error: "Validation failed",
1353
+ code: "VALIDATION_ERROR",
1354
+ details: err.errors.map((e) => ({
1355
+ path: e.path.join("."),
1356
+ message: e.message
1357
+ }))
1365
1358
  });
1366
- return {
1367
- ...data,
1368
- id: docRef.id,
1369
- createdAt: now,
1370
- updatedAt: now
1371
- };
1359
+ return;
1372
1360
  }
1373
- async update(collection, id, data) {
1374
- const docRef = db.collection(collection).doc(id);
1375
- const doc = await docRef.get();
1376
- if (!doc.exists) {
1377
- return null;
1378
- }
1379
- const now = /* @__PURE__ */ new Date();
1380
- await docRef.update({
1381
- ...data,
1382
- updatedAt: now
1361
+ if (err instanceof AppError) {
1362
+ res.status(err.statusCode).json({
1363
+ success: false,
1364
+ error: err.message,
1365
+ code: err.code
1383
1366
  });
1384
- return {
1385
- ...doc.data(),
1386
- ...data,
1387
- id,
1388
- updatedAt: now
1389
- };
1367
+ return;
1390
1368
  }
1391
- async delete(collection, id) {
1392
- const docRef = db.collection(collection).doc(id);
1393
- const doc = await docRef.get();
1394
- if (!doc.exists) {
1395
- return false;
1396
- }
1397
- await docRef.delete();
1398
- return true;
1369
+ if (err.name === "FirebaseError" || err.name === "FirestoreError") {
1370
+ res.status(500).json({
1371
+ success: false,
1372
+ error: "Database error",
1373
+ code: "DATABASE_ERROR"
1374
+ });
1375
+ return;
1399
1376
  }
1400
- async query(collection, filters) {
1401
- let query = db.collection(collection);
1402
- const memoryFilters = [];
1403
- for (const filter of filters) {
1404
- if (["==", "!=", "<", "<=", ">", ">=", "in", "not-in"].includes(filter.op)) {
1405
- query = query.where(filter.field, filter.op, filter.value);
1406
- } else {
1407
- memoryFilters.push(filter);
1408
- }
1409
- }
1410
- const snapshot = await query.get();
1411
- let items = snapshot.docs.map((doc) => ({
1412
- id: doc.id,
1413
- ...doc.data()
1414
- }));
1415
- for (const filter of memoryFilters) {
1416
- items = items.filter((item) => {
1417
- const value = item[filter.field];
1418
- return applyFilterCondition(value, filter.op, filter.value);
1377
+ res.status(500).json({
1378
+ success: false,
1379
+ error: "Internal server error",
1380
+ code: "INTERNAL_ERROR"
1381
+ });
1382
+ };
1383
+ var asyncHandler = (fn) => (req, res, next) => {
1384
+ Promise.resolve(fn(req, res, next)).catch(next);
1385
+ };
1386
+ var notFoundHandler = (req, res) => {
1387
+ res.status(404).json({
1388
+ success: false,
1389
+ error: `Route ${req.method} ${req.path} not found`,
1390
+ code: "ROUTE_NOT_FOUND"
1391
+ });
1392
+ };
1393
+ var validateBody = (schema) => async (req, res, next) => {
1394
+ try {
1395
+ req.body = await schema.parseAsync(req.body);
1396
+ next();
1397
+ } catch (error) {
1398
+ if (error instanceof ZodError) {
1399
+ res.status(400).json({
1400
+ success: false,
1401
+ error: "Validation failed",
1402
+ code: "VALIDATION_ERROR",
1403
+ details: error.errors.map((e) => ({
1404
+ path: e.path.join("."),
1405
+ message: e.message
1406
+ }))
1419
1407
  });
1408
+ return;
1420
1409
  }
1421
- return items;
1410
+ next(error);
1422
1411
  }
1423
- getStore(collection) {
1424
- const svc = this;
1425
- return {
1426
- async getById(id) {
1427
- return svc.getById(collection, id);
1428
- },
1429
- async create(data) {
1430
- return svc.create(collection, data);
1431
- },
1432
- async update(id, data) {
1433
- const result = await svc.update(collection, id, data);
1434
- if (!result) throw new Error(`Entity ${id} not found in ${collection}`);
1435
- return result;
1436
- },
1437
- async delete(id) {
1438
- await svc.delete(collection, id);
1439
- },
1440
- async query(filters) {
1441
- return svc.query(collection, filters);
1442
- }
1443
- };
1412
+ };
1413
+ var validateQuery = (schema) => async (req, res, next) => {
1414
+ try {
1415
+ req.query = await schema.parseAsync(req.query);
1416
+ next();
1417
+ } catch (error) {
1418
+ if (error instanceof ZodError) {
1419
+ res.status(400).json({
1420
+ success: false,
1421
+ error: "Invalid query parameters",
1422
+ code: "VALIDATION_ERROR",
1423
+ details: error.errors.map((e) => ({
1424
+ path: e.path.join("."),
1425
+ message: e.message
1426
+ }))
1427
+ });
1428
+ return;
1429
+ }
1430
+ next(error);
1444
1431
  }
1445
1432
  };
1446
- function createDataService() {
1447
- if (env.USE_MOCK_DATA) {
1448
- logger.info("[DataService] Using MockDataService");
1449
- return new MockDataServiceAdapter();
1433
+ var validateParams = (schema) => async (req, res, next) => {
1434
+ try {
1435
+ req.params = await schema.parseAsync(req.params);
1436
+ next();
1437
+ } catch (error) {
1438
+ if (error instanceof ZodError) {
1439
+ res.status(400).json({
1440
+ success: false,
1441
+ error: "Invalid path parameters",
1442
+ code: "VALIDATION_ERROR",
1443
+ details: error.errors.map((e) => ({
1444
+ path: e.path.join("."),
1445
+ message: e.message
1446
+ }))
1447
+ });
1448
+ return;
1449
+ }
1450
+ next(error);
1450
1451
  }
1451
- logger.info("[DataService] Using FirebaseDataService");
1452
- return new FirebaseDataService();
1453
- }
1454
- var _dataService = null;
1455
- function getDataService() {
1456
- if (!_dataService) {
1457
- _dataService = createDataService();
1452
+ };
1453
+
1454
+ // src/middleware/authenticateFirebase.ts
1455
+ var BEARER_PREFIX = "Bearer ";
1456
+ var DEV_USER = {
1457
+ uid: "dev-user-001",
1458
+ email: "dev@localhost",
1459
+ email_verified: true,
1460
+ aud: "dev-project",
1461
+ auth_time: Math.floor(Date.now() / 1e3),
1462
+ exp: Math.floor(Date.now() / 1e3) + 3600,
1463
+ iat: Math.floor(Date.now() / 1e3),
1464
+ iss: "https://securetoken.google.com/dev-project",
1465
+ sub: "dev-user-001",
1466
+ firebase: {
1467
+ identities: {},
1468
+ sign_in_provider: "custom"
1458
1469
  }
1459
- return _dataService;
1460
- }
1461
- function resetDataService() {
1462
- _dataService = null;
1463
- }
1464
- function seedMockData(entities) {
1465
- if (!env.USE_MOCK_DATA) {
1466
- logger.info("[DataService] Mock mode disabled, skipping seed");
1467
- return;
1470
+ };
1471
+ async function authenticateFirebase(req, res, next) {
1472
+ const authorization = req.headers.authorization;
1473
+ if (env.NODE_ENV === "development" && (!authorization || !authorization.startsWith(BEARER_PREFIX))) {
1474
+ req.firebaseUser = DEV_USER;
1475
+ res.locals.firebaseUser = DEV_USER;
1476
+ return next();
1468
1477
  }
1469
- logger.info("[DataService] Seeding mock data...");
1470
- for (const entity of entities) {
1471
- getMockDataService().seed(entity.name, entity.fields, entity.seedCount);
1478
+ try {
1479
+ if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
1480
+ return res.status(401).json({ error: "Authorization header missing or malformed" });
1481
+ }
1482
+ const token = authorization.slice(BEARER_PREFIX.length);
1483
+ const decodedToken = await getAuth().verifyIdToken(token);
1484
+ req.firebaseUser = decodedToken;
1485
+ res.locals.firebaseUser = decodedToken;
1486
+ return next();
1487
+ } catch (error) {
1488
+ console.error("Firebase authentication failed:", error);
1489
+ return res.status(401).json({ error: "Unauthorized" });
1472
1490
  }
1473
- logger.info("[DataService] Mock data seeding complete");
1474
1491
  }
1475
1492
 
1476
1493
  // src/stores/firestoreFormat.ts
@@ -2371,6 +2388,23 @@ router.get("/active-sessions", async (req, res) => {
2371
2388
  });
2372
2389
  var observability_default = router;
2373
2390
 
2374
- export { AppError, ChangeSetStore, ConflictError, DistributedEventBus, EventBus, EventPersistence, ForbiddenError, InMemoryEventStore, InMemoryServiceRegistry, InMemoryTransport, MockDataService, NotFoundError, RedisTransport, SchemaProtectionService, SchemaStore, ServiceDiscovery, SnapshotStore, UnauthorizedError, ValidationError, ValidationStore, applyFiltersToQuery, asyncHandler, authenticateFirebase, closeWebSocketServer, createServerSkillAgent, db, debugEventsRouter, emitEntityEvent, env, errorHandler, extractPaginationParams, fromFirestoreFormat, getMemoryManager as getAgentMemoryManager, getSessionManager as getAgentSessionManager, getAuth, getConnectedClientCount, getDataService, getFirestore, getMemoryManager, getMockDataService, getServerEventBus, getSessionManager, getWebSocketServer, initializeFirebase, logger, multiUserMiddleware, notFoundHandler, observability_default as observabilityRouter, parseQueryFilters, resetDataService, resetMemoryManager, resetMockDataService, resetServerEventBus, resetSessionManager, seedMockData, setupEventBroadcast, setupStateSyncWebSocket, toFirestoreFormat, validateBody, validateParams, validateQuery, verifyFirebaseAuth };
2391
+ // src/index.ts
2392
+ var dataService = new Proxy({}, {
2393
+ get(_target, prop, receiver) {
2394
+ return Reflect.get(getDataService(), prop, receiver);
2395
+ }
2396
+ });
2397
+ var mockDataService = new Proxy({}, {
2398
+ get(_target, prop, receiver) {
2399
+ return Reflect.get(getMockDataService(), prop, receiver);
2400
+ }
2401
+ });
2402
+ var serverEventBus = new Proxy({}, {
2403
+ get(_target, prop, receiver) {
2404
+ return Reflect.get(getServerEventBus(), prop, receiver);
2405
+ }
2406
+ });
2407
+
2408
+ export { AppError, ChangeSetStore, ConflictError, DistributedEventBus, EventBus, EventPersistence, ForbiddenError, InMemoryEventStore, InMemoryServiceRegistry, InMemoryTransport, MockDataService, NotFoundError, RedisTransport, SchemaProtectionService, SchemaStore, ServiceDiscovery, SnapshotStore, UnauthorizedError, ValidationError, ValidationStore, applyFiltersToQuery, asyncHandler, authenticateFirebase, closeWebSocketServer, createServerSkillAgent, dataService, db, debugEventsRouter, emitEntityEvent, env, errorHandler, extractPaginationParams, fromFirestoreFormat, getMemoryManager as getAgentMemoryManager, getSessionManager as getAgentSessionManager, getAuth, getConnectedClientCount, getDataService, getFirestore, getMemoryManager, getMockDataService, getServerEventBus, getSessionManager, getWebSocketServer, initializeFirebase, logger, mockDataService, multiUserMiddleware, notFoundHandler, observability_default as observabilityRouter, parseQueryFilters, resetDataService, resetMemoryManager, resetMockDataService, resetServerEventBus, resetSessionManager, seedMockData, serverEventBus, setupEventBroadcast, setupStateSyncWebSocket, toFirestoreFormat, validateBody, validateParams, validateQuery, verifyFirebaseAuth };
2375
2409
  //# sourceMappingURL=index.js.map
2376
2410
  //# sourceMappingURL=index.js.map