cloudrx
Version:
TypeScript library for streaming cloud provider events using RxJS with automatic persistence and replay capabilities
175 lines • 6.67 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FatalError = exports.RetryError = exports.CloudProvider = exports.StreamEvent = exports.Abort = void 0;
const rxjs_1 = require("rxjs");
const stream_1 = require("stream");
class Abort extends AbortController {
constructor(signal) {
super();
if (signal) {
signal.addEventListener('abort', () => this.abort(signal.reason));
}
else {
['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'].forEach((signalType) => {
process.on(signalType, () => {
this.abort(new Error(`Aborted by ${signalType}`));
});
});
process.on('beforeExit', (code) => {
this.abort(new Error(`Process exiting with code: ${code}`));
});
process.on('exit', (code) => {
this.abort(new Error(`Process exit with code: ${code}`));
});
process.on('uncaughtException', (error) => {
this.abort(error);
});
process.on('unhandledRejection', (reason) => {
this.abort(reason);
});
}
}
abort(reason) {
console.log('!!! got abort signal:', reason);
super.abort(reason);
}
}
exports.Abort = Abort;
class StreamEvent extends stream_1.EventEmitter {
}
exports.StreamEvent = StreamEvent;
class CloudProvider {
static from(id, opts) {
if (!CloudProvider.instances[id]) {
CloudProvider.instances[id] = new this(id, opts).init();
}
return CloudProvider.instances[id];
}
constructor(id, opts) {
this.id = id;
this._events = new StreamEvent();
this._events.setMaxListeners(100);
this._logger = opts?.logger ?? console;
this._signal = opts?.signal ?? new Abort().signal;
this._signal.addEventListener('abort', () => {
this._events.emit('end');
delete CloudProvider.instances[this.id];
});
}
get logger() {
return this._logger;
}
get signal() {
return this._signal;
}
init() {
return new rxjs_1.Observable((subscriber) => {
if (!this._init$) {
this.logger.debug?.(`[${this.id}] Initializing provider`);
this._init$ = this._init().pipe((0, rxjs_1.shareReplay)(1));
}
else {
this.logger.debug?.(`[${this.id}] Reusing existing init observable`);
}
const subscription = this._init$.subscribe(subscriber);
return () => {
subscription.unsubscribe();
};
});
}
all() {
return new rxjs_1.Observable((subscriber) => {
const subscription = this._stream(true)
.pipe((0, rxjs_1.concatAll)())
.subscribe(subscriber);
return () => {
subscription.unsubscribe();
};
});
}
stream() {
return new rxjs_1.Observable((subscriber) => {
if (!this._stream$) {
this.logger.debug?.(`[${this.id}] Creating new 'latest' stream`);
this._stream$ = this._stream(false).pipe((0, rxjs_1.observeOn)(rxjs_1.asyncScheduler), (0, rxjs_1.shareReplay)(1));
}
else {
this.logger.debug?.(`[${this.id}] Reusing existing 'latest' stream`);
}
this.logger.debug?.(`[${this.id}] Starting stream subscription`);
const subscription = this._stream$
.pipe((0, rxjs_1.takeUntil)((0, rxjs_1.fromEvent)(this._events, 'stop')), (0, rxjs_1.tap)((events) => {
if (events.length > 0) {
this.logger.debug?.(`[${this.id}] Stream emitted ${events.length} events`);
}
this._events.emit('start');
}), (0, rxjs_1.concatAll)())
.subscribe({
next: (event) => subscriber.next(event),
error: (error) => {
this.logger.debug?.(`[${this.id}] Stream error:`, error);
subscriber.error(error);
},
complete: () => {
this.logger.debug?.(`[${this.id}] Stream completed`);
subscriber.complete();
},
});
return () => {
subscription.unsubscribe();
};
});
}
store(item) {
this.logger.debug?.(`[${this.id}] Starting store operation for:`, item);
return new rxjs_1.Observable((subscriber) => {
let stream;
let match;
const matcher$ = this._store(item).pipe((0, rxjs_1.observeOn)(rxjs_1.asyncScheduler), (0, rxjs_1.take)(1), (0, rxjs_1.shareReplay)(1));
const stream$ = this.stream();
this.logger.debug?.(`[${this.id}] Waiting for stream to start`);
this._events.once('start', () => {
this.logger.debug?.(`[${this.id}] Stream started, setting up matcher`);
match = (0, rxjs_1.combineLatest)([stream$, matcher$])
.pipe((0, rxjs_1.takeUntil)((0, rxjs_1.fromEvent)(this._events, 'stop')), (0, rxjs_1.filter)(([event, matcher]) => matcher(event)), (0, rxjs_1.map)(([event]) => {
const streamed = this._unmarshall(event);
this.logger.debug?.(`[${this.id}] Matched event, returning item:`, streamed);
delete streamed.__marker__;
return streamed;
}))
.subscribe((item) => {
this.logger.debug?.(`[${this.id}] Store operation completed`);
subscriber.next(item);
subscriber.complete();
});
});
stream = stream$
.pipe((0, rxjs_1.takeUntil)((0, rxjs_1.fromEvent)(this._events, 'start')))
.subscribe();
return () => {
stream?.unsubscribe();
match?.unsubscribe();
};
});
}
unmarshall(event) {
return this._unmarshall(event);
}
}
exports.CloudProvider = CloudProvider;
CloudProvider.instances = {};
class RetryError extends Error {
constructor(message) {
super(message);
this.name = 'RetryError';
}
}
exports.RetryError = RetryError;
class FatalError extends Error {
constructor(message) {
super(message);
this.name = 'FatalError';
}
}
exports.FatalError = FatalError;
//# sourceMappingURL=base.js.map