open-consul/ui-v2/app/utils/dom/event-source/index.js
John Cowen 0a47a95588 ui: Add DataSource component (#7448)
* ui: Add data-source component and related services (#6486)

* ui: Add data-source component and related services:

1. DataSource component
2. Repository manager for retrieving repositories based on URIs
3. Blocking data service for injection to the data-source component to
support blocking query types of data sources
4. 'Once' promise based data service for injection for potential
fallback to old style promise based data (would need to be injected via
an initial runtime variable)
5. Several utility functions taken from elsewhere
  - maybeCall - a replication of code from elsewhere for condition
  calling a function based on the result of a promise
  - restartWhenAvailable - used for restarting blocking queries when a
  tab is brought to the front
  - ifNotBlocking - to check if blocking is NOT enabled

* Move to a different organization based on protocols

* Don't call open twice when eager

* Workaround new ember error for reading and writing at the same time

* Add first draft of a README.mdx file
2020-05-12 17:14:18 +00:00

147 lines
4.5 KiB
JavaScript

import ObjectProxy from '@ember/object/proxy';
import ArrayProxy from '@ember/array/proxy';
import createListeners from 'consul-ui/utils/dom/create-listeners';
import EventTarget from 'consul-ui/utils/dom/event-target/rsvp';
import cacheFactory from 'consul-ui/utils/dom/event-source/cache';
import proxyFactory from 'consul-ui/utils/dom/event-source/proxy';
import firstResolverFactory from 'consul-ui/utils/dom/event-source/resolver';
import CallableEventSourceFactory from 'consul-ui/utils/dom/event-source/callable';
import OpenableEventSourceFactory from 'consul-ui/utils/dom/event-source/openable';
import BlockingEventSourceFactory from 'consul-ui/utils/dom/event-source/blocking';
import StorageEventSourceFactory from 'consul-ui/utils/dom/event-source/storage';
import EmberObject from '@ember/object';
import { task } from 'ember-concurrency';
import { env } from 'consul-ui/env';
let runner;
switch (env('CONSUL_UI_REALTIME_RUNNER')) {
case 'ec':
runner = function(target, configuration, isClosed) {
return EmberObject.extend({
task: task(function* run() {
while (!isClosed(target)) {
yield target.source.bind(target)(configuration);
}
}),
})
.create()
.get('task')
.perform();
};
break;
case 'generator':
runner = async function(target, configuration, isClosed) {
const run = function*() {
while (!isClosed(target)) {
yield target.source.bind(target)(configuration);
}
};
let step = run().next();
let res;
while (!step.done) {
res = await step.value;
step = run().next();
}
return res;
};
break;
case 'async':
runner = async function(target, configuration, isClosed) {
const run = function() {
return target.source.bind(target)(configuration);
};
let res;
while (!isClosed(target)) {
res = await run();
}
return res;
};
break;
default:
// use the default runner
}
// All The EventSource-i
export const CallableEventSource = CallableEventSourceFactory(EventTarget, Promise, runner);
export const OpenableEventSource = OpenableEventSourceFactory(CallableEventSource);
export const BlockingEventSource = BlockingEventSourceFactory(OpenableEventSource);
export const StorageEventSource = StorageEventSourceFactory(EventTarget, Promise);
// various utils
export const proxy = proxyFactory(ObjectProxy, ArrayProxy, createListeners);
export const resolve = firstResolverFactory(Promise);
export const source = function(source) {
// create API needed for conventional promise blocked, loading, Routes
// i.e. resolve/reject on first response
return resolve(source, createListeners()).then(function(data) {
// create API needed for conventional DD/computed and Controllers
return proxy(source, data);
});
};
export const cache = cacheFactory(source, BlockingEventSource, Promise);
const errorEvent = function(e) {
return new ErrorEvent('error', {
error: e,
message: e.message,
});
};
export const fromPromise = function(promise) {
return new CallableEventSource(function(configuration) {
const dispatch = this.dispatchEvent.bind(this);
const close = () => {
this.close();
};
return promise
.then(function(result) {
close();
dispatch({ type: 'message', data: result });
})
.catch(function(e) {
close();
dispatch(errorEvent(e));
});
});
};
export const toPromise = function(target, cb, eventName = 'message', errorName = 'error') {
return new Promise(function(resolve, reject) {
// TODO: e.target.data
const message = function(e) {
resolve(e.data);
};
const error = function(e) {
reject(e.error);
};
const remove = function() {
if (typeof target.close === 'function') {
target.close();
}
target.removeEventListener(eventName, message);
target.removeEventListener(errorName, error);
};
target.addEventListener(eventName, message);
target.addEventListener(errorName, error);
cb(remove);
});
};
export const once = function(cb, configuration, Source = OpenableEventSource) {
return new Source(function(configuration, source) {
return cb(configuration, source)
.then(function(data) {
source.dispatchEvent({ type: 'message', data: data });
source.close();
})
.catch(function(e) {
source.dispatchEvent({ type: 'error', error: e });
source.close();
});
}, configuration);
};