From 49c81aa9e0b7078d36d6c3d1d16d3743a4470e7d Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sat, 10 Apr 2021 10:39:58 -0700 Subject: [PATCH 1/5] feat(deno): Feathers for Deno core build --- .github/workflows/deno.yml | 18 + deno/LICENSE | 22 ++ deno/_commons/debug.ts | 28 ++ deno/_commons/mod.ts | 105 ++++++ deno/_events/events.js | 501 ++++++++++++++++++++++++++ deno/_feathers/application.ts | 159 ++++++++ deno/_feathers/declarations.ts | 341 ++++++++++++++++++ deno/_feathers/dependencies.ts | 3 + deno/_feathers/events.ts | 33 ++ deno/_feathers/hooks/index.ts | 111 ++++++ deno/_feathers/hooks/legacy.ts | 140 +++++++ deno/_feathers/mod.ts | 19 + deno/_feathers/service.ts | 81 +++++ deno/_feathers/version.ts | 3 + deno/build/events.js | 11 + deno/build/package.js | 57 +++ deno/mod.ts | 1 + deno/readme.md | 9 + deno/test.ts | 24 ++ deno/tsconfig.json | 5 + packages/feathers/src/application.ts | 9 +- packages/feathers/src/declarations.ts | 7 +- packages/feathers/src/dependencies.ts | 5 + packages/feathers/src/events.ts | 4 +- packages/feathers/src/hooks/index.ts | 8 +- packages/feathers/src/hooks/legacy.ts | 2 +- packages/feathers/src/index.ts | 9 +- packages/feathers/src/service.ts | 3 +- 28 files changed, 1696 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/deno.yml create mode 100644 deno/LICENSE create mode 100644 deno/_commons/debug.ts create mode 100644 deno/_commons/mod.ts create mode 100644 deno/_events/events.js create mode 100644 deno/_feathers/application.ts create mode 100644 deno/_feathers/declarations.ts create mode 100644 deno/_feathers/dependencies.ts create mode 100644 deno/_feathers/events.ts create mode 100644 deno/_feathers/hooks/index.ts create mode 100644 deno/_feathers/hooks/legacy.ts create mode 100644 deno/_feathers/mod.ts create mode 100644 deno/_feathers/service.ts create mode 100644 deno/_feathers/version.ts create mode 100644 deno/build/events.js create mode 100644 deno/build/package.js create mode 100644 deno/mod.ts create mode 100644 deno/readme.md create mode 100644 deno/test.ts create mode 100644 deno/tsconfig.json create mode 100644 packages/feathers/src/dependencies.ts diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml new file mode 100644 index 0000000000..40cac8cf0d --- /dev/null +++ b/.github/workflows/deno.yml @@ -0,0 +1,18 @@ +name: Deno CI + +on: [push, pull_request] + +jobs: + test: + runs-on: "ubuntu-latest" + steps: + - name: Setup repo + uses: actions/checkout@v2 + + - name: Setup Deno + uses: denolib/setup-deno@v2 + with: + deno-version: v1.x + + - name: Run Tests + run: deno test --config deno/tsconfig.json deno/test.ts diff --git a/deno/LICENSE b/deno/LICENSE new file mode 100644 index 0000000000..3f395cc665 --- /dev/null +++ b/deno/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2021 Feathers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/deno/_commons/debug.ts b/deno/_commons/debug.ts new file mode 100644 index 0000000000..ee441b8c4b --- /dev/null +++ b/deno/_commons/debug.ts @@ -0,0 +1,28 @@ +// DO NOT MODIFY - generated from packages/commons/src/debug.ts + +export type DebugFunction = (...args: any[]) => void; +export type DebugInitializer = (name: string) => DebugFunction; + +const debuggers: { [key: string]: DebugFunction } = {}; + +export function noopDebug (): DebugFunction { + return () => {} +} + +let defaultInitializer: DebugInitializer = noopDebug; + +export function setDebug (debug: DebugInitializer) { + defaultInitializer = debug; + + Object.keys(debuggers).forEach(name => { + debuggers[name] = debug(name); + }); +} + +export function createDebug (name: string) { + if (!debuggers[name]) { + debuggers[name] = defaultInitializer(name); + } + + return (...args: any[]) => debuggers[name](...args); +} diff --git a/deno/_commons/mod.ts b/deno/_commons/mod.ts new file mode 100644 index 0000000000..2c6fb4b0e1 --- /dev/null +++ b/deno/_commons/mod.ts @@ -0,0 +1,105 @@ +// DO NOT MODIFY - generated from packages/commons/src/index.ts + +// Removes all leading and trailing slashes from a path +export function stripSlashes (name: string) { + return name.replace(/^(\/+)|(\/+)$/g, ''); +} + +export type KeyValueCallback = (value: any, key: string) => T; + +// A set of lodash-y utility functions that use ES6 +export const _ = { + each (obj: any, callback: KeyValueCallback) { + if (obj && typeof obj.forEach === 'function') { + obj.forEach(callback); + } else if (_.isObject(obj)) { + Object.keys(obj).forEach(key => callback(obj[key], key)); + } + }, + + some (value: any, callback: KeyValueCallback) { + return Object.keys(value) + .map(key => [ value[key], key ]) + .some(([val, key]) => callback(val, key)); + }, + + every (value: any, callback: KeyValueCallback) { + return Object.keys(value) + .map(key => [ value[key], key ]) + .every(([val, key]) => callback(val, key)); + }, + + keys (obj: any) { + return Object.keys(obj); + }, + + values (obj: any) { + return _.keys(obj).map(key => obj[key]); + }, + + isMatch (obj: any, item: any) { + return _.keys(item).every(key => obj[key] === item[key]); + }, + + isEmpty (obj: any) { + return _.keys(obj).length === 0; + }, + + isObject (item: any) { + return (typeof item === 'object' && !Array.isArray(item) && item !== null); + }, + + isObjectOrArray (value: any) { + return typeof value === 'object' && value !== null; + }, + + extend (first: any, ...rest: any[]) { + return Object.assign(first, ...rest); + }, + + omit (obj: any, ...keys: string[]) { + const result = _.extend({}, obj); + keys.forEach(key => delete result[key]); + return result; + }, + + pick (source: any, ...keys: string[]) { + return keys.reduce((result: { [key: string]: any }, key) => { + if (source[key] !== undefined) { + result[key] = source[key]; + } + + return result; + }, {}); + }, + + // Recursively merge the source object into the target object + merge (target: any, source: any) { + if (_.isObject(target) && _.isObject(source)) { + Object.keys(source).forEach(key => { + if (_.isObject(source[key])) { + if (!target[key]) { + Object.assign(target, { [key]: {} }); + } + + _.merge(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + }); + } + return target; + } +}; + +// Duck-checks if an object looks like a promise +export function isPromise (result: any) { + return _.isObject(result) && + typeof result.then === 'function'; +} + +export function createSymbol (name: string) { + return typeof Symbol !== 'undefined' ? Symbol(name) : name; +} + +export * from './debug.ts'; diff --git a/deno/_events/events.js b/deno/_events/events.js new file mode 100644 index 0000000000..062e0ce602 --- /dev/null +++ b/deno/_events/events.js @@ -0,0 +1,501 @@ +// DO NOT MODIFY - generated from node_modules/events/events.js +const module = {}; +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} +module.exports = EventEmitter; +module.exports.once = once; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +function checkListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } +} + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + checkListener(listener); + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function once(emitter, name) { + return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } + + function resolver() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener('error', errorListener); + } + resolve([].slice.call(arguments)); + }; + + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== 'error') { + addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + } + }); +} + +function addErrorHandlerIfEventEmitter(emitter, handler, flags) { + if (typeof emitter.on === 'function') { + eventTargetAgnosticAddListener(emitter, 'error', handler, flags); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === 'function') { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen for `error` events here. + emitter.addEventListener(name, function wrapListener(arg) { + // IE does not have builtin `{ once: true }` support so we + // have to do it manually. + if (flags.once) { + emitter.removeEventListener(name, wrapListener); + } + listener(arg); + }); + } else { + throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + } +} + +export { EventEmitter }; \ No newline at end of file diff --git a/deno/_feathers/application.ts b/deno/_feathers/application.ts new file mode 100644 index 0000000000..2cbb49986f --- /dev/null +++ b/deno/_feathers/application.ts @@ -0,0 +1,159 @@ +// DO NOT MODIFY - generated from packages/feathers/src/application.ts + +import version from './version.ts'; +import { + EventEmitter, stripSlashes, createDebug, HOOKS +} from './dependencies.ts'; +import { eventHook, eventMixin } from './events.ts'; +import { hookMixin } from './hooks/index.ts'; +import { wrapService, getServiceOptions } from './service.ts'; +import { + FeathersApplication, + ServiceMixin, + Service, + ServiceOptions, + ServiceInterface, + Application, + HookOptions, + FeathersService, + HookMap, + LegacyHookMap +} from './declarations.ts'; +import { enableLegacyHooks } from './hooks/legacy.ts'; + +const debug = createDebug('@feathersjs/feathers'); + +export class Feathers extends EventEmitter implements FeathersApplication { + services: ServiceTypes = ({} as ServiceTypes); + settings: AppSettings = ({} as AppSettings); + mixins: ServiceMixin>[] = [ hookMixin, eventMixin ]; + version: string = version; + _isSetup = false; + appHooks: HookMap, any> = { + [HOOKS]: [ (eventHook as any) ] + }; + + private legacyHooks: (this: any, allHooks: any) => any; + + constructor () { + super(); + this.legacyHooks = enableLegacyHooks(this); + } + + get (name: L): AppSettings[L] { + return this.settings[name]; + } + + set (name: L, value: AppSettings[L]) { + this.settings[name] = value; + return this; + } + + configure (callback: (this: this, app: this) => void) { + callback.call(this, this); + + return this; + } + + defaultService (location: string): ServiceInterface { + throw new Error(`Can not find service '${location}'`); + } + + service ( + location: L + ): FeathersService : ServiceTypes[L]> { + const path = (stripSlashes(location) || '/') as L; + const current = this.services[path]; + + if (typeof current === 'undefined') { + this.use(path, this.defaultService(path) as any); + return this.service(path); + } + + return current as any; + } + + use ( + path: L, + service: keyof any extends keyof ServiceTypes ? ServiceInterface | Application : ServiceTypes[L], + options?: ServiceOptions + ): this { + if (typeof path !== 'string') { + throw new Error(`'${path}' is not a valid service path.`); + } + + const location = (stripSlashes(path) || '/') as L; + const subApp = service as Application; + const isSubApp = typeof subApp.service === 'function' && subApp.services; + + if (isSubApp) { + Object.keys(subApp.services).forEach(subPath => + this.use(`${location}/${subPath}` as any, subApp.service(subPath) as any) + ); + + return this; + } + + const protoService = wrapService(location, service, options); + const serviceOptions = getServiceOptions(service, options); + + debug(`Registering new service at \`${location}\``); + + // Add all the mixins + this.mixins.forEach(fn => fn.call(this, protoService, location, serviceOptions)); + + // If we ran setup already, set this service up explicitly, this will not `await` + if (this._isSetup && typeof protoService.setup === 'function') { + debug(`Setting up service for \`${location}\``); + protoService.setup(this, location); + } + + this.services[location] = protoService; + + return this; + } + + hooks (hookMap: HookOptions) { + const legacyMap = hookMap as LegacyHookMap; + + if (legacyMap.before || legacyMap.after || legacyMap.error) { + return this.legacyHooks(legacyMap); + } + + if (Array.isArray(hookMap)) { + this.appHooks[HOOKS].push(...hookMap as any); + } else { + const methodHookMap = hookMap as HookMap, any>; + + Object.keys(methodHookMap).forEach(key => { + const methodHooks = this.appHooks[key] || []; + + this.appHooks[key] = methodHooks.concat(methodHookMap[key]); + }); + } + + return this; + } + + setup () { + let promise = Promise.resolve(); + + // Setup each service (pass the app so that they can look up other services etc.) + for (const path of Object.keys(this.services)) { + promise = promise.then(() => { + const service: any = this.service(path as any); + + if (typeof service.setup === 'function') { + debug(`Setting up service for \`${path}\``); + + return service.setup(this, path); + } + }); + } + + return promise.then(() => { + this._isSetup = true; + return this; + }); + } +} diff --git a/deno/_feathers/declarations.ts b/deno/_feathers/declarations.ts new file mode 100644 index 0000000000..58019b76ef --- /dev/null +++ b/deno/_feathers/declarations.ts @@ -0,0 +1,341 @@ +// DO NOT MODIFY - generated from packages/feathers/src/declarations.ts + +import { + EventEmitter, NextFunction, HookContext as BaseHookContext +} from './dependencies.ts'; + +type SelfOrArray = S | S[]; +type OptionalPick = Pick> + +export type { NextFunction }; + +export interface ServiceOptions { + events?: string[]; + methods?: string[]; + serviceEvents?: string[]; +} + +export interface ServiceMethods> { + find (params?: Params): Promise; + + get (id: Id, params?: Params): Promise; + + create (data: D, params?: Params): Promise; + + update (id: NullableId, data: D, params?: Params): Promise; + + patch (id: NullableId, data: D, params?: Params): Promise; + + remove (id: NullableId, params?: Params): Promise; + + setup (app: Application, path: string): Promise; +} + +export interface ServiceOverloads { + create? (data: D[], params?: Params): Promise; + + update? (id: Id, data: D, params?: Params): Promise; + + update? (id: null, data: D, params?: Params): Promise; + + patch? (id: Id, data: D, params?: Params): Promise; + + patch? (id: null, data: D, params?: Params): Promise; + + remove? (id: Id, params?: Params): Promise; + + remove? (id: null, params?: Params): Promise; +} + +export type Service> = + ServiceMethods & + ServiceOverloads; + +export type ServiceInterface> = + Partial>; + +export interface ServiceAddons extends EventEmitter { + id?: string; + hooks (options: HookOptions): this; +} + +export interface ServiceHookOverloads { + find ( + params: Params, + context: HookContext + ): Promise; + + get ( + id: Id, + params: Params, + context: HookContext + ): Promise; + + create ( + data: ServiceGenericData | ServiceGenericData[], + params: Params, + context: HookContext + ): Promise; + + update ( + id: NullableId, + data: ServiceGenericData, + params: Params, + context: HookContext + ): Promise; + + patch ( + id: NullableId, + data: ServiceGenericData, + params: Params, + context: HookContext + ): Promise; + + remove ( + id: NullableId, + params: Params, + context: HookContext + ): Promise; +} + +export type FeathersService> = + S & ServiceAddons & OptionalPick, keyof S>; + +export type CustomMethod = { + [k in Methods]: (data: any, params?: Params) => Promise; +} + +export type ServiceMixin = (service: FeathersService, path: string, options?: ServiceOptions) => void; + +export type ServiceGenericType = S extends ServiceInterface ? T : any; +export type ServiceGenericData = S extends ServiceInterface ? D : any; + +export interface FeathersApplication { + /** + * The Feathers application version + */ + version: string; + + /** + * A list of callbacks that run when a new service is registered + */ + mixins: ServiceMixin>[]; + + /** + * The index of all services keyed by their path. + * + * __Important:__ Services should always be retrieved via `app.service('name')` + * not via `app.services`. + */ + services: ServiceTypes; + + /** + * The application settings that can be used via + * `app.get` and `app.set` + */ + settings: AppSettings; + + /** + * A private-ish indicator if `app.setup()` has been called already + */ + _isSetup: boolean; + + /** + * Contains all registered application level hooks. + */ + appHooks: HookMap, any>; + + /** + * Retrieve an application setting by name + * + * @param name The setting name + */ + get (name: L): AppSettings[L]; + + /** + * Set an application setting + * + * @param name The setting name + * @param value The setting value + */ + set (name: L, value: AppSettings[L]): this; + + /** + * Runs a callback configure function with the current application instance. + * + * @param callback The callback `(app: Application) => {}` to run + */ + configure (callback: (this: this, app: this) => void): this; + + /** + * Returns a fallback service instance that will be registered + * when no service was found. Usually throws a `NotFound` error + * but also used to instantiate client side services. + * + * @param location The path of the service + */ + defaultService (location: string): ServiceInterface; + + /** + * Register a new service or a sub-app. When passed another + * Feathers application, all its services will be re-registered + * with the `path` prefix. + * + * @param path The path for the service to register + * @param service The service object to register or another + * Feathers application to use a sub-app under the `path` prefix. + * @param options The options for this service + */ + use ( + path: L, + service: keyof any extends keyof ServiceTypes ? ServiceInterface | Application : ServiceTypes[L], + options?: ServiceOptions + ): this; + + /** + * Get the Feathers service instance for a path. This will + * be the service originally registered with Feathers functionality + * like hooks and events added. + * + * @param path The name of the service. + */ + service ( + path: L + ): FeathersService : ServiceTypes[L]>; + + setup (server?: any): Promise; + + /** + * Register application level hooks. + * + * @param map The application hook settings. + */ + hooks (map: HookOptions): this; +} + +// This needs to be an interface instead of a type +// so that the declaration can be extended by other modules +export interface Application extends FeathersApplication, EventEmitter { + +} + +export type Id = number | string; +export type NullableId = Id | null; + +export interface Query { + [key: string]: any; +} + +export interface Params { + query?: Query; + provider?: string; + route?: { [key: string]: string }; + headers?: { [key: string]: any }; + [key: string]: any; // (JL) not sure if we want this +} + +export interface HookContext extends BaseHookContext> { + /** + * A read only property that contains the Feathers application object. This can be used to + * retrieve other services (via context.app.service('name')) or configuration values. + */ + readonly app: A; + /** + * A read only property with the name of the service method (one of find, get, + * create, update, patch, remove). + */ + readonly method: string; + /** + * A read only property and contains the service name (or path) without leading or + * trailing slashes. + */ + readonly path: string; + /** + * A read only property and contains the service this hook currently runs on. + */ + readonly service: S; + /** + * A read only property with the hook type (one of before, after or error). + * Will be `null` for asynchronous hooks. + */ + readonly type: null | 'before' | 'after' | 'error'; + /** + * The list of method arguments. Should not be modified, modify the + * `params`, `data` and `id` properties instead. + */ + readonly arguments: any[]; + /** + * A writeable property containing the data of a create, update and patch service + * method call. + */ + data?: ServiceGenericData; + /** + * A writeable property with the error object that was thrown in a failed method call. + * It is only available in error hooks. + */ + error?: any; + /** + * A writeable property and the id for a get, remove, update and patch service + * method call. For remove, update and patch context.id can also be null when + * modifying multiple entries. In all other cases it will be undefined. + */ + id?: Id; + /** + * A writeable property that contains the service method parameters (including + * params.query). + */ + params: Params; + /** + * A writeable property containing the result of the successful service method call. + * It is only available in after hooks. + * + * `context.result` can also be set in + * + * - A before hook to skip the actual service method (database) call + * - An error hook to swallow the error and return a result instead + */ + result?: ServiceGenericType; + /** + * A writeable, optional property and contains a 'safe' version of the data that + * should be sent to any client. If context.dispatch has not been set context.result + * will be sent to the client instead. + */ + dispatch?: ServiceGenericType; + /** + * A writeable, optional property that allows to override the standard HTTP status + * code that should be returned. + */ + statusCode?: number; + /** + * The event emitted by this method. Can be set to `null` to skip event emitting. + */ + event: string|null; +} + +// Legacy hook typings +export type LegacyHookFunction> = + (this: S, context: HookContext) => (Promise | void> | HookContext | void); + +type LegacyHookMethodMap = + { [L in keyof S]?: SelfOrArray>; } & + { all?: SelfOrArray> }; + +type LegacyHookTypeMap = + SelfOrArray> | LegacyHookMethodMap; + +export type LegacyHookMap = { + before?: LegacyHookTypeMap, + after?: LegacyHookTypeMap, + error?: LegacyHookTypeMap +} + +// New @feathersjs/hook typings +export type HookFunction> = + (context: HookContext, next: NextFunction) => Promise; + +export type HookMap = { + [L in keyof S]?: HookFunction[]; +}; + +export type HookOptions = + HookMap | HookFunction[] | LegacyHookMap; diff --git a/deno/_feathers/dependencies.ts b/deno/_feathers/dependencies.ts new file mode 100644 index 0000000000..fbff98b956 --- /dev/null +++ b/deno/_feathers/dependencies.ts @@ -0,0 +1,3 @@ +export * from 'https://deno.land/x/hooks@v0.6.3/deno/index.ts'; +export * from '../_events/events.js' +export * from '../_commons/mod.ts'; \ No newline at end of file diff --git a/deno/_feathers/events.ts b/deno/_feathers/events.ts new file mode 100644 index 0000000000..153c9f2d37 --- /dev/null +++ b/deno/_feathers/events.ts @@ -0,0 +1,33 @@ +// DO NOT MODIFY - generated from packages/feathers/src/events.ts + +import { NextFunction, EventEmitter } from './dependencies.ts'; +import { HookContext, FeathersService } from './declarations.ts'; +import { getServiceOptions, defaultEventMap } from './service.ts'; + +export function eventHook (context: HookContext, next: NextFunction) { + const { events } = getServiceOptions((context as any).self); + const defaultEvent = (defaultEventMap as any)[context.method] || null; + + context.event = defaultEvent; + + return next().then(() => { + // Send the event only if the service does not do so already (indicated in the `events` option) + // This is used for custom events and for client services receiving event from the server + if (typeof context.event === 'string' && !events.includes(context.event)) { + const results = Array.isArray(context.result) ? context.result : [ context.result ]; + + results.forEach(element => (context as any).self.emit(context.event, element, context)); + } + }); +} + +export function eventMixin (service: FeathersService) { + const isEmitter = typeof service.on === 'function' && + typeof service.emit === 'function'; + + if (!isEmitter) { + Object.assign(service, EventEmitter.prototype); + } + + return service; +} diff --git a/deno/_feathers/hooks/index.ts b/deno/_feathers/hooks/index.ts new file mode 100644 index 0000000000..f26352ca16 --- /dev/null +++ b/deno/_feathers/hooks/index.ts @@ -0,0 +1,111 @@ +// DO NOT MODIFY - generated from packages/feathers/src/hooks/index.ts + +import { + getManager, HookContextData, HookManager, HookMap, HOOKS, hooks, Middleware +} from '../dependencies.ts'; +import { + Service, ServiceOptions, HookContext, FeathersService, Application +} from '../declarations.ts'; +import { defaultServiceArguments, getHookMethods } from '../service.ts'; +import { + collectLegacyHooks, + enableLegacyHooks, + fromAfterHook, + fromBeforeHook, + fromErrorHooks +} from './legacy.ts'; + +export { fromAfterHook, fromBeforeHook, fromErrorHooks }; + +export function createContext (service: Service, method: string, data: HookContextData = {}) { + const createContext = (service as any)[method].createContext; + + if (typeof createContext !== 'function') { + throw new Error(`Can not create context for method ${method}`); + } + + return createContext(data) as HookContext; +} + +export class FeathersHookManager extends HookManager { + constructor (public app: A, public method: string) { + super(); + this._middleware = []; + } + + collectMiddleware (self: any, args: any[]): Middleware[] { + const app = this.app as any as Application; + const appHooks = app.appHooks[HOOKS].concat(app.appHooks[this.method] || []); + const legacyAppHooks = collectLegacyHooks(this.app, this.method); + const middleware = super.collectMiddleware(self, args); + const legacyHooks = collectLegacyHooks(self, this.method); + + return [...appHooks, ...legacyAppHooks, ...middleware, ...legacyHooks]; + } + + initializeContext (self: any, args: any[], context: HookContext) { + const ctx = super.initializeContext(self, args, context); + + ctx.params = ctx.params || {}; + + return ctx; + } + + middleware (mw: Middleware[]) { + this._middleware.push(...mw); + return this; + } +} + +export function hookMixin ( + this: A, service: FeathersService, path: string, options: ServiceOptions +) { + if (typeof service.hooks === 'function') { + return service; + } + + const app = this; + const serviceMethodHooks = getHookMethods(service, options).reduce((res, method) => { + const params = (defaultServiceArguments as any)[method] || [ 'data', 'params' ]; + + res[method] = new FeathersHookManager(app, method) + .params(...params) + .props({ + app, + path, + method, + service, + event: null, + type: null + }); + + return res; + }, {} as HookMap); + const handleLegacyHooks = enableLegacyHooks(service); + + hooks(service, serviceMethodHooks); + + service.hooks = function (this: any, hookOptions: any) { + if (hookOptions.before || hookOptions.after || hookOptions.error) { + return handleLegacyHooks.call(this, hookOptions); + } + + if (Array.isArray(hookOptions)) { + return hooks(this, hookOptions); + } + + Object.keys(hookOptions).forEach(method => { + const manager = getManager(this[method]); + + if (!(manager instanceof FeathersHookManager)) { + throw new Error(`Method ${method} is not a Feathers hooks enabled service method`); + } + + manager.middleware(hookOptions[method]); + }); + + return this; + } + + return service; +} diff --git a/deno/_feathers/hooks/legacy.ts b/deno/_feathers/hooks/legacy.ts new file mode 100644 index 0000000000..1f31bcf6cd --- /dev/null +++ b/deno/_feathers/hooks/legacy.ts @@ -0,0 +1,140 @@ +// DO NOT MODIFY - generated from packages/feathers/src/hooks/legacy.ts + +import { _ } from '../dependencies.ts'; +import { LegacyHookFunction } from '../declarations.ts'; + +const { each } = _; + +export function fromBeforeHook (hook: LegacyHookFunction) { + return (context: any, next: any) => { + context.type = 'before'; + + return Promise.resolve(hook.call(context.self, context)).then(() => { + context.type = null; + return next(); + }); + }; +} + +export function fromAfterHook (hook: LegacyHookFunction) { + return (context: any, next: any) => { + return next().then(() => { + context.type = 'after'; + return hook.call(context.self, context) + }).then(() => { + context.type = null; + }); + } +} + +export function fromErrorHooks (hooks: LegacyHookFunction[]) { + return (context: any, next: any) => { + return next().catch((error: any) => { + let promise: Promise = Promise.resolve(); + + context.original = { ...context }; + context.error = error; + context.type = 'error'; + + delete context.result; + + for (const hook of hooks) { + promise = promise.then(() => hook.call(context.self, context)) + } + + return promise.then(() => { + context.type = null; + + if (context.result === undefined) { + throw context.error; + } + }); + }); + } +} + +export function collectLegacyHooks (target: any, method: string) { + const { + before: { [method]: before = [] }, + after: { [method]: after = [] }, + error: { [method]: error = [] } + } = target.__hooks; + const beforeHooks = before; + const afterHooks = [...after].reverse(); + const errorHook = fromErrorHooks(error); + + return [errorHook, ...beforeHooks, ...afterHooks]; +} + +// Converts different hook registration formats into the +// same internal format +export function convertHookData (obj: any) { + let hook: any = {}; + + if (Array.isArray(obj)) { + hook = { all: obj }; + } else if (typeof obj !== 'object') { + hook = { all: [ obj ] }; + } else { + each(obj, function (value, key) { + hook[key] = !Array.isArray(value) ? [ value ] : value; + }); + } + + return hook; +} + +// Add `.hooks` functionality to an object +export function enableLegacyHooks ( + obj: any, + methods: string[] = ['find', 'get', 'create', 'update', 'patch', 'remove'], + types: string[] = ['before', 'after', 'error'] +) { + const hookData: any = {}; + + types.forEach(type => { + // Initialize properties where hook functions are stored + hookData[type] = {}; + }); + + // Add non-enumerable `__hooks` property to the object + Object.defineProperty(obj, '__hooks', { + configurable: true, + value: hookData, + writable: true + }); + + return function legacyHooks (this: any, allHooks: any) { + each(allHooks, (current: any, type) => { + if (!this.__hooks[type]) { + throw new Error(`'${type}' is not a valid hook type`); + } + + const hooks = convertHookData(current); + + each(hooks, (_value, method) => { + if (method !== 'all' && methods.indexOf(method) === -1) { + throw new Error(`'${method}' is not a valid hook method`); + } + }); + + methods.forEach(method => { + let currentHooks = [...(hooks.all || []), ...(hooks[method] || [])]; + + this.__hooks[type][method] = this.__hooks[type][method] || []; + + if (type === 'before') { + currentHooks = currentHooks.map(fromBeforeHook); + } + + if (type === 'after') { + currentHooks = currentHooks.map(fromAfterHook); + } + + this.__hooks[type][method].push(...currentHooks); + }); + }); + + return this; + } +} diff --git a/deno/_feathers/mod.ts b/deno/_feathers/mod.ts new file mode 100644 index 0000000000..92428e8931 --- /dev/null +++ b/deno/_feathers/mod.ts @@ -0,0 +1,19 @@ +// DO NOT MODIFY - generated from packages/feathers/src/index.ts + +import { setDebug } from './dependencies.ts'; +import version from './version.ts'; +import { Feathers } from './application.ts'; +import { Application } from './declarations.ts'; + +export function feathers () { + return new Feathers() as Application; +} + +feathers.setDebug = setDebug; + +export { version, Feathers }; +export * from './hooks/index.ts'; +export * from './declarations.ts'; +export * from './service.ts'; + + diff --git a/deno/_feathers/service.ts b/deno/_feathers/service.ts new file mode 100644 index 0000000000..4c792c9f41 --- /dev/null +++ b/deno/_feathers/service.ts @@ -0,0 +1,81 @@ +// DO NOT MODIFY - generated from packages/feathers/src/service.ts + +import { createSymbol } from './dependencies.ts'; +import { ServiceOptions } from './declarations.ts'; + +export const SERVICE = createSymbol('@feathersjs/service'); + +export const defaultServiceArguments = { + find: [ 'params' ], + get: [ 'id', 'params' ], + create: [ 'data', 'params' ], + update: [ 'id', 'data', 'params' ], + patch: [ 'id', 'data', 'params' ], + remove: [ 'id', 'params' ] +} + +export const defaultServiceMethods = Object.keys(defaultServiceArguments); + +export const defaultEventMap = { + create: 'created', + update: 'updated', + patch: 'patched', + remove: 'removed' +} + +export function getHookMethods (service: any, options: ServiceOptions) { + const { methods } = options; + + return defaultServiceMethods.filter(m => + typeof service[m] === 'function' && !methods.includes(m) + ).concat(methods); +} + +export function getServiceOptions ( + service: any, options: ServiceOptions = {} +): ServiceOptions { + const existingOptions = service[SERVICE]; + + if (existingOptions) { + return existingOptions; + } + + const { + methods = defaultServiceMethods.filter(method => + typeof service[method] === 'function' + ), + events = service.events || [] + } = options; + const { + serviceEvents = Object.values(defaultEventMap).concat(events) + } = options; + + return { + ...options, + events, + methods, + serviceEvents + }; +} + +export function wrapService ( + location: string, service: any, options: ServiceOptions +) { + // Do nothing if this is already an initialized + if (service[SERVICE]) { + return service; + } + + const protoService = Object.create(service); + const serviceOptions = getServiceOptions(service, options); + + if (Object.keys(serviceOptions.methods).length === 0 && typeof service.setup !== 'function') { + throw new Error(`Invalid service object passed for path \`${location}\``); + } + + Object.defineProperty(protoService, SERVICE, { + value: serviceOptions + }); + + return protoService; +} diff --git a/deno/_feathers/version.ts b/deno/_feathers/version.ts new file mode 100644 index 0000000000..39fdd6bc04 --- /dev/null +++ b/deno/_feathers/version.ts @@ -0,0 +1,3 @@ +// DO NOT MODIFY - generated from packages/feathers/src/version.ts + +export default 'development'; diff --git a/deno/build/events.js b/deno/build/events.js new file mode 100644 index 0000000000..052192d6da --- /dev/null +++ b/deno/build/events.js @@ -0,0 +1,11 @@ +const fs = require('fs'); +const path = require('path'); + +const name = 'events/events.js'; +const file = require.resolve(name); +const result = `// DO NOT MODIFY - generated from node_modules/events/events.js +const module = {}; +${fs.readFileSync(file).toString()} +export { EventEmitter };`; + +fs.writeFileSync(path.join(__dirname, '..', `_${name}`), result); diff --git a/deno/build/package.js b/deno/build/package.js new file mode 100644 index 0000000000..b0cb246361 --- /dev/null +++ b/deno/build/package.js @@ -0,0 +1,57 @@ +// A build script that converts the incompatible +// TypeScript/Deno module references +const path = require('path'); +const fs = require('fs'); + +const [,,packageName] = process.argv; +const excludes = [ 'dependencies.ts' ]; +const listFiles = (folder, base = '') => { + const files = fs.readdirSync(folder); + + return files.reduce((result, current) => { + const fullName = path.join(folder, current); + if (fs.lstatSync(fullName).isDirectory()) { + result.push(...listFiles(fullName, path.join(base, current))); + } else if (!excludes.includes(current) && path.extname(current) === '.ts') { + result.push(path.join(base, current)); + } + + return result; + }, []); +} + +const folder = path.join(__dirname, '..', '..', 'packages', packageName, 'src'); +const target = path.join(__dirname, '..', `_${packageName}`); +const files = listFiles(folder); +const existingFiles = listFiles(target); + +existingFiles.forEach(fileName => { + fs.unlinkSync(path.join(target, fileName)); +}); + +files.forEach(fileName => { + const parts = fileName.split('/'); + const fullName = path.join(folder, fileName); + const content = fs.readFileSync(fullName).toString(); + const transformed = content + .replace(/from '(\..*)'/g, 'from \'$1.ts\'') + .replace(/if \(typeof module !== 'undefined'\) {.*}/gms, ''); + const result = `// DO NOT MODIFY - generated from packages/${packageName}/src/${fileName}\n\n${transformed}`; + + // Creates all sub-folders + parts.pop(); + + if (parts.length) { + let currentPath = target; + for (part of parts) { + currentPath = path.join(currentPath, part); + if (!fs.existsSync(currentPath)) { + fs.mkdirSync(currentPath); + } + } + } + + const targetName = fileName === 'index.ts' ? 'mod.ts' : fileName; + + fs.writeFileSync(path.join(target, targetName), result); +}); diff --git a/deno/mod.ts b/deno/mod.ts new file mode 100644 index 0000000000..05cc021507 --- /dev/null +++ b/deno/mod.ts @@ -0,0 +1 @@ +export * from './_feathers/mod.ts'; diff --git a/deno/readme.md b/deno/readme.md new file mode 100644 index 0000000000..cc8c860705 --- /dev/null +++ b/deno/readme.md @@ -0,0 +1,9 @@ +Feathers logo + +## A framework for real-time applications and REST APIs with Deno + +This folder contains the Deno build of Feathers. + +## Use with Deno + + diff --git a/deno/test.ts b/deno/test.ts new file mode 100644 index 0000000000..002b448b20 --- /dev/null +++ b/deno/test.ts @@ -0,0 +1,24 @@ +import { assertEquals } from 'https://deno.land/std@0.91.0/testing/asserts.ts'; +import { feathers } from './mod.ts'; + +class MyService { + async create (data: any) { + return data; + } +} + +Deno.test('instantiates a Feathers app with a service', async () => { + const app = feathers(); + const data = { + message: 'hi' + }; + + app.use('test', new MyService()); + + const eventPromise = new Promise(resolve => { + app.service('test').once('created', (data: any) => resolve(data)); + }); + + assertEquals(await app.service('test').create(data), data); + assertEquals(await eventPromise, data); +}); diff --git a/deno/tsconfig.json b/deno/tsconfig.json new file mode 100644 index 0000000000..d48119c4fb --- /dev/null +++ b/deno/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strictNullChecks": false + } +} \ No newline at end of file diff --git a/packages/feathers/src/application.ts b/packages/feathers/src/application.ts index 543e04ea63..dc95cf9f58 100644 --- a/packages/feathers/src/application.ts +++ b/packages/feathers/src/application.ts @@ -1,10 +1,9 @@ -import { EventEmitter } from 'events'; -import { stripSlashes, createDebug } from '@feathersjs/commons'; -import { HOOKS } from '@feathersjs/hooks'; - import version from './version'; +import { + EventEmitter, stripSlashes, createDebug, HOOKS +} from './dependencies'; import { eventHook, eventMixin } from './events'; -import { hookMixin } from './hooks'; +import { hookMixin } from './hooks/index'; import { wrapService, getServiceOptions } from './service'; import { FeathersApplication, diff --git a/packages/feathers/src/declarations.ts b/packages/feathers/src/declarations.ts index 88e3efeb71..e4c7bb9ef6 100644 --- a/packages/feathers/src/declarations.ts +++ b/packages/feathers/src/declarations.ts @@ -1,12 +1,11 @@ -import { EventEmitter } from 'events'; import { - NextFunction, HookContext as BaseHookContext -} from '@feathersjs/hooks'; + EventEmitter, NextFunction, HookContext as BaseHookContext +} from './dependencies'; type SelfOrArray = S | S[]; type OptionalPick = Pick> -export { NextFunction }; +export type { NextFunction }; export interface ServiceOptions { events?: string[]; diff --git a/packages/feathers/src/dependencies.ts b/packages/feathers/src/dependencies.ts new file mode 100644 index 0000000000..7363399681 --- /dev/null +++ b/packages/feathers/src/dependencies.ts @@ -0,0 +1,5 @@ +import { EventEmitter } from 'events'; +export * from '@feathersjs/commons'; +export * from '@feathersjs/hooks'; + +export { EventEmitter }; diff --git a/packages/feathers/src/events.ts b/packages/feathers/src/events.ts index d675409ee0..20eb162681 100644 --- a/packages/feathers/src/events.ts +++ b/packages/feathers/src/events.ts @@ -1,6 +1,4 @@ -import { NextFunction } from '@feathersjs/hooks'; -import { EventEmitter } from 'events'; - +import { NextFunction, EventEmitter } from './dependencies'; import { HookContext, FeathersService } from './declarations'; import { getServiceOptions, defaultEventMap } from './service'; diff --git a/packages/feathers/src/hooks/index.ts b/packages/feathers/src/hooks/index.ts index d8be284c86..5c9879664a 100644 --- a/packages/feathers/src/hooks/index.ts +++ b/packages/feathers/src/hooks/index.ts @@ -1,5 +1,9 @@ -import { getManager, HookContextData, HookManager, HookMap, HOOKS, hooks, Middleware } from '@feathersjs/hooks'; -import { Service, ServiceOptions, HookContext, FeathersService, Application } from '../declarations'; +import { + getManager, HookContextData, HookManager, HookMap, HOOKS, hooks, Middleware +} from '../dependencies'; +import { + Service, ServiceOptions, HookContext, FeathersService, Application +} from '../declarations'; import { defaultServiceArguments, getHookMethods } from '../service'; import { collectLegacyHooks, diff --git a/packages/feathers/src/hooks/legacy.ts b/packages/feathers/src/hooks/legacy.ts index 1bed723433..041ac6b08b 100644 --- a/packages/feathers/src/hooks/legacy.ts +++ b/packages/feathers/src/hooks/legacy.ts @@ -1,4 +1,4 @@ -import { _ } from '@feathersjs/commons'; +import { _ } from '../dependencies'; import { LegacyHookFunction } from '../declarations'; const { each } = _; diff --git a/packages/feathers/src/index.ts b/packages/feathers/src/index.ts index 1c7ed4eda9..0280adc2e9 100644 --- a/packages/feathers/src/index.ts +++ b/packages/feathers/src/index.ts @@ -1,5 +1,4 @@ -import * as commons from '@feathersjs/commons'; - +import { setDebug } from './dependencies'; import version from './version'; import { Feathers } from './application'; import { Application } from './declarations'; @@ -8,12 +7,12 @@ export function feathers () { return new Feathers() as Application; } -feathers.setDebug = commons.setDebug; +feathers.setDebug = setDebug; -export { version, commons, Feathers }; +export { version, Feathers }; +export * from './hooks/index'; export * from './declarations'; export * from './service'; -export * from './hooks'; if (typeof module !== 'undefined') { module.exports = Object.assign(feathers, module.exports); diff --git a/packages/feathers/src/service.ts b/packages/feathers/src/service.ts index c0507977c3..e6bcfa91df 100644 --- a/packages/feathers/src/service.ts +++ b/packages/feathers/src/service.ts @@ -1,5 +1,4 @@ -import { createSymbol } from '@feathersjs/commons'; - +import { createSymbol } from './dependencies'; import { ServiceOptions } from './declarations'; export const SERVICE = createSymbol('@feathersjs/service'); From 2586e78bc6dfd579b6f20a6535959c835dee4e9d Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sat, 10 Apr 2021 11:20:16 -0700 Subject: [PATCH 2/5] Update Codeclimate settings --- .codeclimate.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 0e3f7f947c..4343c26245 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -40,4 +40,5 @@ exclude_patterns: - "**/adapter-tests/*" - "**/dist/*" - "**/*.dist.js" - - "**/templates/*" \ No newline at end of file + - "**/templates/*" + - "**/_**" \ No newline at end of file From 0045e760143dcd30b208f81b0e7f5bcb9c55dd7c Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 11 Apr 2021 09:41:54 -0700 Subject: [PATCH 3/5] Add errors build and skeleton for server implementation --- deno/_errors/mod.ts | 275 ++++++++++++++++++++++++++++++++++++++++++++ deno/server/http.ts | 25 ++++ 2 files changed, 300 insertions(+) create mode 100644 deno/_errors/mod.ts create mode 100644 deno/server/http.ts diff --git a/deno/_errors/mod.ts b/deno/_errors/mod.ts new file mode 100644 index 0000000000..f5a526c9f2 --- /dev/null +++ b/deno/_errors/mod.ts @@ -0,0 +1,275 @@ +// DO NOT MODIFY - generated from packages/errors/src/index.ts + +export interface FeathersErrorJSON { + name: string; + message: string; + code: number; + className: string; + data?: any; + errors?: any; +} + +export type DynamicError = Error & { [key: string]: any }; +export type ErrorMessage = string | DynamicError | { [key: string]: any } | any[]; + +interface ErrorProperties extends Omit { + type: string; +} + +export class FeathersError extends Error { + readonly type: string; + readonly code: number; + readonly className: string; + readonly data: any; + readonly errors: any; + + constructor (err: ErrorMessage, name: string, code: number, className: string, _data: any) { + let msg = typeof err === 'string' ? err : 'Error'; + const properties: ErrorProperties = { + name, + code, + className, + type: 'FeathersError' + }; + + if (Array.isArray(_data)) { + properties.data = _data; + } else if (typeof err === 'object' || _data !== undefined) { + const { message, errors, ...rest } = typeof err === 'object' ? err : _data; + + msg = message || msg; + properties.errors = errors; + properties.data = rest; + } + + super(msg); + Object.assign(this, properties); + } + + toJSON () { + const result: FeathersErrorJSON = { + name: this.name, + message: this.message, + code: this.code, + className: this.className + }; + + if (this.data !== undefined) { + result.data = this.data; + } + + if (this.errors !== undefined) { + result.errors = this.errors; + } + + return result; + } +} + +export class BadRequest extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'BadRequest', 400, 'bad-request', data); + } +} + +// 401 - Not Authenticated +export class NotAuthenticated extends FeathersError{ + constructor (message?: ErrorMessage, data?: any) { + super(message, 'NotAuthenticated', 401, 'not-authenticated', data); + } +} + +// 402 - Payment Error +export class PaymentError extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'PaymentError', 402, 'payment-error', data); + } +} + +// 403 - Forbidden +export class Forbidden extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Forbidden', 403, 'forbidden', data); + } +} + +// 404 - Not Found +export class NotFound extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'NotFound', 404, 'not-found', data); + } +} + +// 405 - Method Not Allowed +export class MethodNotAllowed extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'MethodNotAllowed', 405, 'method-not-allowed', data); + } +} + +// 406 - Not Acceptable +export class NotAcceptable extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'NotAcceptable', 406, 'not-acceptable', data); + } +} + +// 408 - Timeout +export class Timeout extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Timeout', 408, 'timeout', data); + } +} + +// 409 - Conflict +export class Conflict extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Conflict', 409, 'conflict', data); + } +} + +// 410 - Gone +export class Gone extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Gone', 410, 'gone', data); + } +} + +// 411 - Length Required +export class LengthRequired extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'LengthRequired', 411, 'length-required', data); + } +} + +// 422 Unprocessable +export class Unprocessable extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Unprocessable', 422, 'unprocessable', data); + } +} + +// 429 Too Many Requests +export class TooManyRequests extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'TooManyRequests', 429, 'too-many-requests', data); + } +} + +// 500 - General Error +export class GeneralError extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'GeneralError', 500, 'general-error', data); + } +} + +// 501 - Not Implemented +export class NotImplemented extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'NotImplemented', 501, 'not-implemented', data); + } +} + +// 502 - Bad Gateway +export class BadGateway extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'BadGateway', 502, 'bad-gateway', data); + } +} + +// 503 - Unavailable +export class Unavailable extends FeathersError { + constructor (message?: ErrorMessage, data?: any) { + super(message, 'Unavailable', 503, 'unavailable', data); + } +} + +export interface Errors { + FeathersError: FeathersError; + BadRequest: BadRequest; + NotAuthenticated: NotAuthenticated; + PaymentError: PaymentError; + Forbidden: Forbidden; + NotFound: NotFound; + MethodNotAllowed: MethodNotAllowed; + NotAcceptable: NotAcceptable; + Timeout: Timeout; + Conflict: Conflict; + LengthRequired: LengthRequired; + Unprocessable: Unprocessable; + TooManyRequests: TooManyRequests; + GeneralError: GeneralError; + NotImplemented: NotImplemented; + BadGateway: BadGateway; + Unavailable: Unavailable; + 400: BadRequest; + 401: NotAuthenticated; + 402: PaymentError; + 403: Forbidden; + 404: NotFound; + 405: MethodNotAllowed; + 406: NotAcceptable; + 408: Timeout; + 409: Conflict; + 411: LengthRequired; + 422: Unprocessable; + 429: TooManyRequests; + 500: GeneralError; + 501: NotImplemented; + 502: BadGateway; + 503: Unavailable; +} + +export const errors = { + FeathersError, + BadRequest, + NotAuthenticated, + PaymentError, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + Timeout, + Conflict, + LengthRequired, + Unprocessable, + TooManyRequests, + GeneralError, + NotImplemented, + BadGateway, + Unavailable, + 400: BadRequest, + 401: NotAuthenticated, + 402: PaymentError, + 403: Forbidden, + 404: NotFound, + 405: MethodNotAllowed, + 406: NotAcceptable, + 408: Timeout, + 409: Conflict, + 410: Gone, + 411: LengthRequired, + 422: Unprocessable, + 429: TooManyRequests, + 500: GeneralError, + 501: NotImplemented, + 502: BadGateway, + 503: Unavailable +} + +export function convert (error: any) { + if (!error) { + return error; + } + + const FeathersError = (errors as any)[error.name]; + const result = FeathersError + ? new FeathersError(error.message, error.data) + : new Error(error.message || error); + + if (typeof error === 'object') { + Object.assign(result, error); + } + + return result; +} diff --git a/deno/server/http.ts b/deno/server/http.ts new file mode 100644 index 0000000000..13dc6bc269 --- /dev/null +++ b/deno/server/http.ts @@ -0,0 +1,25 @@ +import { serve } from 'https://deno.land/std/http/server.ts'; + +// deno run --allow-net --allow-read standard-library.ts +// Docs: https://deno.land/std/http +const s = serve({ port: 8000 }) + +console.log(`🦕 Deno server running at http://localhost:8000/ 🦕`) + +for await (const req of s) { + req.respond({ body: 'Hello from your first Deno server' }) +} + +export function rest () { + return (app: any) => { + app.listen = async function (port: number) { + const s = serve({ port: 8000 }); + + console.log(`🦕 Deno server running at http://localhost:8000/ 🦕`) + + for await (const req of s) { + req.respond({ body: 'Hello from your first Deno server' }) + } + } + } +} From 5591a8dbdd70d734facbc462fb3ed35cedb98333 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Mon, 19 Apr 2021 10:26:09 -0700 Subject: [PATCH 4/5] Update Deno readme --- deno/readme.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/deno/readme.md b/deno/readme.md index cc8c860705..6b8ef62fc5 100644 --- a/deno/readme.md +++ b/deno/readme.md @@ -6,4 +6,33 @@ This folder contains the Deno build of Feathers. ## Use with Deno +```ts +// app.ts +import { feathers } from 'https://deno.land/x/feathers@v5.0.0-pre.3/mod.ts'; +type Message { + message: string; +} + +class MyService { + async create (data: Message) { + return data; + } +} + +type ServiceTypes { + myservice: MyService +} + +const app = feathers(); + +app.use('myservice', new MyService()); + +app.service('myservice').on('created', (data: Message) => { + console.log('Created', data); +}); + +await app.service('myservice').create({ + message: 'Hello from Deno' +}); +``` From db809460b1080dc5c27429dfaa9f24b46b247e6f Mon Sep 17 00:00:00 2001 From: David Luecke Date: Tue, 20 Apr 2021 16:37:24 -0700 Subject: [PATCH 5/5] Update fixed hooks dependency --- deno/_feathers/dependencies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno/_feathers/dependencies.ts b/deno/_feathers/dependencies.ts index fbff98b956..3fc60b6999 100644 --- a/deno/_feathers/dependencies.ts +++ b/deno/_feathers/dependencies.ts @@ -1,3 +1,3 @@ -export * from 'https://deno.land/x/hooks@v0.6.3/deno/index.ts'; +export * from 'https://deno.land/x/hooks@v0.6.5/deno/index.ts'; export * from '../_events/events.js' export * from '../_commons/mod.ts'; \ No newline at end of file