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/README.md +585 -30
- package/dist/index.cjs +203 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -5
- package/dist/index.d.ts +57 -5
- package/dist/index.js +203 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
-
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
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;
|