fauxbase 0.4.0 → 0.5.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.
package/dist/index.cjs CHANGED
@@ -183,6 +183,8 @@ var Service = class {
183
183
  driver;
184
184
  resourceName;
185
185
  client;
186
+ /** @internal */
187
+ _eventBus;
186
188
  /** @internal — called by createClient to wire the service */
187
189
  _init(driver, resourceName) {
188
190
  this.driver = driver;
@@ -203,27 +205,56 @@ var Service = class {
203
205
  await this.runHooks("beforeCreate", data, allItems);
204
206
  const result = await this.driver.create(this.resourceName, data);
205
207
  await this.runHooks("afterCreate", result.data);
208
+ this.emitEvent("created", { data: result.data, id: result.data.id });
206
209
  return result;
207
210
  }
208
211
  async update(id, data) {
209
212
  await this.runHooks("beforeUpdate", id, data);
210
213
  const result = await this.driver.update(this.resourceName, id, data);
211
214
  await this.runHooks("afterUpdate", result.data);
215
+ this.emitEvent("updated", { data: result.data, id });
212
216
  return result;
213
217
  }
214
218
  async delete(id) {
215
- return this.driver.delete(this.resourceName, id);
219
+ const result = await this.driver.delete(this.resourceName, id);
220
+ this.emitEvent("deleted", { data: result.data, id });
221
+ return result;
216
222
  }
217
223
  async count(filter) {
218
224
  return this.driver.count(this.resourceName, filter);
219
225
  }
220
226
  get bulk() {
227
+ const self = this;
221
228
  return {
222
- create: (items) => this.driver.bulkCreate(this.resourceName, items),
223
- update: (updates) => this.driver.bulkUpdate(this.resourceName, updates),
224
- delete: (ids) => this.driver.bulkDelete(this.resourceName, ids)
229
+ async create(items) {
230
+ const result = await self.driver.bulkCreate(self.resourceName, items);
231
+ self.emitEvent("bulkCreated", { data: result.data });
232
+ return result;
233
+ },
234
+ async update(updates) {
235
+ const result = await self.driver.bulkUpdate(self.resourceName, updates);
236
+ self.emitEvent("bulkUpdated", { data: result.data, ids: updates.map((u) => u.id) });
237
+ return result;
238
+ },
239
+ async delete(ids) {
240
+ const result = await self.driver.bulkDelete(self.resourceName, ids);
241
+ self.emitEvent("bulkDeleted", { ids });
242
+ return result;
243
+ }
225
244
  };
226
245
  }
246
+ emitEvent(action, extra) {
247
+ if (!this._eventBus) return;
248
+ this._eventBus.emit({
249
+ action,
250
+ resource: this.resourceName,
251
+ data: extra.data,
252
+ id: extra.id,
253
+ ids: extra.ids,
254
+ timestamp: Date.now(),
255
+ source: "local"
256
+ });
257
+ }
227
258
  async runHooks(hookType, ...args) {
228
259
  const methods = getHooks(this.constructor, hookType);
229
260
  for (const methodName of methods) {
@@ -1360,6 +1391,146 @@ var HttpDriver = class {
1360
1391
  }
1361
1392
  };
1362
1393
 
1394
+ // src/events/event-bus.ts
1395
+ var EventBus = class {
1396
+ listeners = /* @__PURE__ */ new Map();
1397
+ anyListeners = /* @__PURE__ */ new Set();
1398
+ on(resource, handler) {
1399
+ if (!this.listeners.has(resource)) {
1400
+ this.listeners.set(resource, /* @__PURE__ */ new Set());
1401
+ }
1402
+ this.listeners.get(resource).add(handler);
1403
+ return () => {
1404
+ this.listeners.get(resource)?.delete(handler);
1405
+ };
1406
+ }
1407
+ onAny(handler) {
1408
+ this.anyListeners.add(handler);
1409
+ return () => {
1410
+ this.anyListeners.delete(handler);
1411
+ };
1412
+ }
1413
+ emit(event) {
1414
+ const resourceListeners = this.listeners.get(event.resource);
1415
+ if (resourceListeners) {
1416
+ for (const handler of resourceListeners) {
1417
+ handler(event);
1418
+ }
1419
+ }
1420
+ for (const handler of this.anyListeners) {
1421
+ handler(event);
1422
+ }
1423
+ }
1424
+ destroy() {
1425
+ this.listeners.clear();
1426
+ this.anyListeners.clear();
1427
+ }
1428
+ };
1429
+
1430
+ // src/events/sse-source.ts
1431
+ var SSESource = class {
1432
+ constructor(config, eventBus) {
1433
+ this.config = config;
1434
+ this.eventBus = eventBus;
1435
+ }
1436
+ eventSource = null;
1437
+ connect() {
1438
+ this.eventSource = new EventSource(this.config.url, {
1439
+ withCredentials: this.config.withCredentials
1440
+ });
1441
+ for (const [eventType, resource] of Object.entries(this.config.eventMap)) {
1442
+ this.eventSource.addEventListener(eventType, (e) => {
1443
+ const parsed = this.parseEvent(e, resource);
1444
+ if (parsed) {
1445
+ this.eventBus.emit(parsed);
1446
+ }
1447
+ });
1448
+ }
1449
+ }
1450
+ disconnect() {
1451
+ if (this.eventSource) {
1452
+ this.eventSource.close();
1453
+ this.eventSource = null;
1454
+ }
1455
+ }
1456
+ parseEvent(e, resource) {
1457
+ try {
1458
+ const raw = JSON.parse(e.data);
1459
+ return {
1460
+ action: raw.action,
1461
+ resource,
1462
+ data: raw.data,
1463
+ id: raw.id,
1464
+ ids: raw.ids,
1465
+ timestamp: raw.timestamp ?? Date.now(),
1466
+ source: "remote"
1467
+ };
1468
+ } catch {
1469
+ return null;
1470
+ }
1471
+ }
1472
+ };
1473
+
1474
+ // src/events/stomp-source.ts
1475
+ var STOMPSource = class {
1476
+ constructor(config, eventBus) {
1477
+ this.config = config;
1478
+ this.eventBus = eventBus;
1479
+ }
1480
+ client = null;
1481
+ connect() {
1482
+ this.connectAsync();
1483
+ }
1484
+ async connectAsync() {
1485
+ let StompJs;
1486
+ try {
1487
+ const moduleName = "@stomp/stompjs";
1488
+ StompJs = await Function("m", "return import(m)")(moduleName);
1489
+ } catch {
1490
+ throw new Error(
1491
+ "STOMP source requires @stomp/stompjs. Install it: npm install @stomp/stompjs"
1492
+ );
1493
+ }
1494
+ this.client = new StompJs.Client({
1495
+ brokerURL: this.config.brokerUrl,
1496
+ connectHeaders: this.config.connectHeaders,
1497
+ onConnect: () => {
1498
+ for (const [destination, resource] of Object.entries(this.config.subscriptions)) {
1499
+ this.client.subscribe(destination, (message) => {
1500
+ const parsed = this.parseMessage(message, resource);
1501
+ if (parsed) {
1502
+ this.eventBus.emit(parsed);
1503
+ }
1504
+ });
1505
+ }
1506
+ }
1507
+ });
1508
+ this.client.activate();
1509
+ }
1510
+ disconnect() {
1511
+ if (this.client) {
1512
+ this.client.deactivate();
1513
+ this.client = null;
1514
+ }
1515
+ }
1516
+ parseMessage(message, resource) {
1517
+ try {
1518
+ const raw = JSON.parse(message.body);
1519
+ return {
1520
+ action: raw.action,
1521
+ resource,
1522
+ data: raw.data,
1523
+ id: raw.id,
1524
+ ids: raw.ids,
1525
+ timestamp: raw.timestamp ?? Date.now(),
1526
+ source: "remote"
1527
+ };
1528
+ } catch {
1529
+ return null;
1530
+ }
1531
+ }
1532
+ };
1533
+
1363
1534
  // src/seed.ts
1364
1535
  function seed(entityClass, data) {
1365
1536
  const entityName = entityClass.name.toLowerCase();
@@ -1458,6 +1629,33 @@ function createClient(config) {
1458
1629
  svc._setClient(client);
1459
1630
  }
1460
1631
  }
1632
+ let eventSource = null;
1633
+ if (config.events) {
1634
+ const eventBus = new EventBus();
1635
+ client._eventBus = eventBus;
1636
+ for (const [name, ServiceClass] of Object.entries(config.services)) {
1637
+ const svc = client[name];
1638
+ svc._eventBus = eventBus;
1639
+ }
1640
+ const eventsConfig = config.events === true ? {} : config.events;
1641
+ if (eventsConfig.handlers) {
1642
+ for (const [resource, handler] of Object.entries(eventsConfig.handlers)) {
1643
+ eventBus.on(resource, handler);
1644
+ }
1645
+ }
1646
+ if (eventsConfig.source) {
1647
+ if (eventsConfig.source.type === "sse") {
1648
+ eventSource = new SSESource(eventsConfig.source, eventBus);
1649
+ } else if (eventsConfig.source.type === "stomp") {
1650
+ eventSource = new STOMPSource(eventsConfig.source, eventBus);
1651
+ }
1652
+ eventSource?.connect();
1653
+ }
1654
+ client.disconnect = () => {
1655
+ eventSource?.disconnect();
1656
+ eventBus.destroy();
1657
+ };
1658
+ }
1461
1659
  let readyPromise;
1462
1660
  if (defaultDriver instanceof LocalDriver) {
1463
1661
  if (defaultDriver.isReady) {
@@ -1506,6 +1704,7 @@ function applySeedsIfNeeded(driver, seeds) {
1506
1704
  exports.AuthService = AuthService;
1507
1705
  exports.ConflictError = ConflictError;
1508
1706
  exports.Entity = Entity;
1707
+ exports.EventBus = EventBus;
1509
1708
  exports.FauxbaseError = FauxbaseError;
1510
1709
  exports.ForbiddenError = ForbiddenError;
1511
1710
  exports.HttpDriver = HttpDriver;