@feathersjs/transport-commons 5.0.0-pre.3 → 5.0.0-pre.30

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +182 -201
  2. package/LICENSE +1 -1
  3. package/README.md +2 -2
  4. package/client.d.ts +1 -1
  5. package/lib/channels/channel/base.js +2 -2
  6. package/lib/channels/channel/base.js.map +1 -1
  7. package/lib/channels/channel/combined.js +2 -2
  8. package/lib/channels/channel/combined.js.map +1 -1
  9. package/lib/channels/index.d.ts +12 -10
  10. package/lib/channels/index.js +15 -10
  11. package/lib/channels/index.js.map +1 -1
  12. package/lib/channels/mixins.d.ts +3 -3
  13. package/lib/channels/mixins.js +3 -3
  14. package/lib/channels/mixins.js.map +1 -1
  15. package/lib/client.d.ts +2 -2
  16. package/lib/client.js +10 -12
  17. package/lib/client.js.map +1 -1
  18. package/lib/http.d.ts +35 -0
  19. package/lib/http.js +77 -0
  20. package/lib/http.js.map +1 -0
  21. package/lib/index.d.ts +3 -2
  22. package/lib/index.js +27 -1
  23. package/lib/index.js.map +1 -1
  24. package/lib/routing/index.d.ts +9 -4
  25. package/lib/routing/index.js +30 -14
  26. package/lib/routing/index.js.map +1 -1
  27. package/lib/routing/router.d.ts +8 -3
  28. package/lib/routing/router.js +64 -22
  29. package/lib/routing/router.js.map +1 -1
  30. package/lib/socket/index.js +10 -10
  31. package/lib/socket/index.js.map +1 -1
  32. package/lib/socket/utils.js +43 -53
  33. package/lib/socket/utils.js.map +1 -1
  34. package/package.json +19 -15
  35. package/src/channels/channel/base.ts +28 -28
  36. package/src/channels/channel/combined.ts +31 -31
  37. package/src/channels/index.ts +67 -61
  38. package/src/channels/mixins.ts +49 -46
  39. package/src/client.ts +70 -70
  40. package/src/http.ts +95 -0
  41. package/src/index.ts +5 -4
  42. package/src/routing/index.ts +50 -26
  43. package/src/routing/router.ts +91 -42
  44. package/src/socket/index.ts +44 -44
  45. package/src/socket/utils.ts +64 -57
package/src/http.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { MethodNotAllowed } from '@feathersjs/errors/lib'
2
+ import { HookContext, NullableId, Params } from '@feathersjs/feathers'
3
+ import encodeUrl from 'encodeurl'
4
+
5
+ export const METHOD_HEADER = 'x-service-method'
6
+
7
+ export interface ServiceParams {
8
+ id: NullableId
9
+ data: any
10
+ params: Params
11
+ }
12
+
13
+ export const statusCodes = {
14
+ created: 201,
15
+ noContent: 204,
16
+ methodNotAllowed: 405,
17
+ success: 200,
18
+ seeOther: 303
19
+ }
20
+
21
+ export const knownMethods: { [key: string]: string } = {
22
+ post: 'create',
23
+ patch: 'patch',
24
+ put: 'update',
25
+ delete: 'remove'
26
+ }
27
+
28
+ export function getServiceMethod(_httpMethod: string, id: unknown, headerOverride?: string) {
29
+ const httpMethod = _httpMethod.toLowerCase()
30
+
31
+ if (httpMethod === 'post' && headerOverride) {
32
+ return headerOverride
33
+ }
34
+
35
+ const mappedMethod = knownMethods[httpMethod]
36
+
37
+ if (mappedMethod) {
38
+ return mappedMethod
39
+ }
40
+
41
+ if (httpMethod === 'get') {
42
+ return id === null ? 'find' : 'get'
43
+ }
44
+
45
+ throw new MethodNotAllowed(`Method ${_httpMethod} not allowed`)
46
+ }
47
+
48
+ export const argumentsFor = {
49
+ get: ({ id, params }: ServiceParams) => [id, params],
50
+ find: ({ params }: ServiceParams) => [params],
51
+ create: ({ data, params }: ServiceParams) => [data, params],
52
+ update: ({ id, data, params }: ServiceParams) => [id, data, params],
53
+ patch: ({ id, data, params }: ServiceParams) => [id, data, params],
54
+ remove: ({ id, params }: ServiceParams) => [id, params],
55
+ default: ({ data, params }: ServiceParams) => [data, params]
56
+ }
57
+
58
+ export function getStatusCode(context: HookContext, body: any, location: string | string[]) {
59
+ const { http = {} } = context
60
+
61
+ if (http.status) {
62
+ return http.status
63
+ }
64
+
65
+ if (context.method === 'create') {
66
+ return statusCodes.created
67
+ }
68
+
69
+ if (location !== undefined) {
70
+ return statusCodes.seeOther
71
+ }
72
+
73
+ if (!body) {
74
+ return statusCodes.noContent
75
+ }
76
+
77
+ return statusCodes.success
78
+ }
79
+
80
+ export function getResponse(context: HookContext) {
81
+ const { http = {} } = context
82
+ const body = context.dispatch !== undefined ? context.dispatch : context.result
83
+
84
+ let headers = http.headers || {}
85
+ let location = headers.Location
86
+
87
+ if (http.location !== undefined) {
88
+ location = encodeUrl(http.location)
89
+ headers = { ...headers, Location: location }
90
+ }
91
+
92
+ const status = getStatusCode(context, body, location)
93
+
94
+ return { status, headers, body }
95
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { socket } from './socket';
2
- import { routing } from './routing';
3
- import { channels } from './channels';
1
+ import { socket } from './socket'
2
+ import { routing } from './routing'
3
+ import { channels, Channel, CombinedChannel, RealTimeConnection } from './channels'
4
4
 
5
- export { socket, routing, channels };
5
+ export * as http from './http'
6
+ export { socket, routing, channels, Channel, CombinedChannel, RealTimeConnection }
@@ -1,45 +1,69 @@
1
- import { Application, Service } from '@feathersjs/feathers';
2
- import { Router } from './router';
1
+ import { Application, FeathersService, Service, ServiceOptions } from '@feathersjs/feathers'
2
+ import { Router } from './router'
3
3
 
4
4
  declare module '@feathersjs/feathers/lib/declarations' {
5
5
  interface RouteLookup {
6
- service: Service<any>,
7
- params: { [key: string]: string }
6
+ service: Service
7
+ params: { [key: string]: any }
8
8
  }
9
9
 
10
- interface Application<ServiceTypes, AppSettings> { // eslint-disable-line
11
- routes: Router<any>;
12
- lookup (path: string): RouteLookup;
10
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
+ interface Application<Services, Settings> {
12
+ // eslint-disable-line
13
+ routes: Router<{
14
+ service: Service
15
+ params?: { [key: string]: any }
16
+ }>
17
+ lookup(path: string): RouteLookup
13
18
  }
14
19
  }
15
20
 
16
- export * from './router';
21
+ export * from './router'
22
+
23
+ const lookup = function (this: Application, path: string) {
24
+ const result = this.routes.lookup(path)
25
+
26
+ if (result === null) {
27
+ return null
28
+ }
29
+
30
+ const {
31
+ params: colonParams,
32
+ data: { service, params: dataParams }
33
+ } = result
34
+
35
+ const params = dataParams ? { ...dataParams, ...colonParams } : colonParams
36
+
37
+ return { service, params }
38
+ }
17
39
 
18
40
  export const routing = () => (app: Application) => {
19
41
  if (typeof app.lookup === 'function') {
20
- return;
42
+ return
21
43
  }
22
44
 
23
- const routes = new Router();
24
-
25
- Object.assign(app, {
26
- routes,
27
- lookup (this: Application, path: string) {
28
- const result = this.routes.lookup(path);
45
+ app.routes = new Router()
46
+ app.lookup = lookup
29
47
 
30
- if (result !== null) {
31
- const { params, data: service } = result;
48
+ // This mixin allows us to unregister a service. It needs to run
49
+ // first so that `teardown` hooks still get registered properly
50
+ app.mixins.unshift((service: Service) => {
51
+ const { teardown } = service
32
52
 
33
- return { params, service };
53
+ service.teardown = async function (app: Application, path: string) {
54
+ if (typeof teardown === 'function') {
55
+ await teardown.call(this, app, path)
34
56
  }
35
-
36
- return result;
57
+ app.routes.remove(path)
58
+ app.routes.remove(`${path}/:__id`)
37
59
  }
38
- });
60
+ })
39
61
 
40
62
  // Add a mixin that registers a service on the router
41
- app.mixins.push((service: Service<any>, path: string) => {
42
- app.routes.insert(path, service);
43
- app.routes.insert(`${path}/:__id`, service);
44
- });
45
- };
63
+ app.mixins.push((service: FeathersService, path: string, options: ServiceOptions) => {
64
+ const { routeParams: params = {} } = options
65
+
66
+ app.routes.insert(path, { service, params })
67
+ app.routes.insert(`${path}/:__id`, { service, params })
68
+ })
69
+ }
@@ -1,87 +1,136 @@
1
- import { stripSlashes } from '@feathersjs/commons';
2
- import { BadRequest } from '@feathersjs/errors';
1
+ import { stripSlashes } from '@feathersjs/commons'
3
2
 
4
3
  export interface LookupData {
5
- params: { [key: string]: string };
4
+ params: { [key: string]: string }
6
5
  }
7
6
 
8
7
  export interface LookupResult<T> extends LookupData {
9
- data?: T;
8
+ data?: T
10
9
  }
11
10
 
12
11
  export class RouteNode<T = any> {
13
- data?: T;
14
- children: { [key: string]: RouteNode } = {};
15
- placeholder?: RouteNode;
12
+ data?: T
13
+ children: { [key: string]: RouteNode } = {}
14
+ placeholders: RouteNode[] = []
16
15
 
17
- constructor (public name: string) {}
16
+ constructor(public name: string, public depth: number) {}
18
17
 
19
- insert (path: string[], data: T): RouteNode<T> {
20
- if (path.length === 0) {
21
- this.data = data;
22
- return this;
18
+ get hasChildren() {
19
+ return Object.keys(this.children).length !== 0 || this.placeholders.length !== 0
20
+ }
21
+
22
+ insert(path: string[], data: T): RouteNode<T> {
23
+ if (this.depth === path.length) {
24
+ if (this.data !== undefined) {
25
+ throw new Error(`Path ${path.join('/')} already exists`)
26
+ }
27
+
28
+ this.data = data
29
+ return this
23
30
  }
24
31
 
25
- const [ current, ...rest ] = path;
32
+ const current = path[this.depth]
33
+ const nextDepth = this.depth + 1
26
34
 
27
35
  if (current.startsWith(':')) {
28
- const { placeholder } = this;
29
- const name = current.substring(1);
36
+ // Insert a placeholder node like /messages/:id
37
+ const placeholderName = current.substring(1)
38
+ let placeholder = this.placeholders.find((p) => p.name === placeholderName)
30
39
 
31
40
  if (!placeholder) {
32
- this.placeholder = new RouteNode(name);
33
- } else if(placeholder.name !== name) {
34
- throw new BadRequest(`Can not add route with placeholder ':${name}' because placeholder ':${placeholder.name}' already exists`);
41
+ placeholder = new RouteNode(placeholderName, nextDepth)
42
+ this.placeholders.push(placeholder)
35
43
  }
36
44
 
37
- return this.placeholder.insert(rest, data);
45
+ return placeholder.insert(path, data)
38
46
  }
39
47
 
40
- this.children[current] = this.children[current] || new RouteNode(current);
48
+ const child = this.children[current] || new RouteNode(current, nextDepth)
49
+
50
+ this.children[current] = child
41
51
 
42
- return this.children[current].insert(rest, data);
52
+ return child.insert(path, data)
43
53
  }
44
54
 
45
- lookup (path: string[], info: LookupData): LookupResult<T>|null {
46
- if (path.length === 0) {
47
- return {
48
- ...info,
49
- data: this.data
55
+ remove(path: string[]) {
56
+ if (path.length === this.depth) {
57
+ return
58
+ }
59
+
60
+ const current = path[this.depth]
61
+
62
+ if (current.startsWith(':')) {
63
+ const placeholderName = current.substring(1)
64
+ const placeholder = this.placeholders.find((p) => p.name === placeholderName)
65
+
66
+ placeholder.remove(path)
67
+ this.placeholders = this.placeholders.filter((p) => p !== placeholder)
68
+ } else if (this.children[current]) {
69
+ const child = this.children[current]
70
+
71
+ child.remove(path)
72
+
73
+ if (!child.hasChildren) {
74
+ delete this.children[current]
50
75
  }
51
76
  }
77
+ }
52
78
 
53
- const [ current, ...rest ] = path;
54
- const child = this.children[current];
79
+ lookup(path: string[], info: LookupData): LookupResult<T> | null {
80
+ if (path.length === this.depth) {
81
+ return this.data === undefined
82
+ ? null
83
+ : {
84
+ ...info,
85
+ data: this.data
86
+ }
87
+ }
88
+
89
+ const current = path[this.depth]
90
+ const child = this.children[current]
55
91
 
56
92
  if (child) {
57
- return child.lookup(rest, info);
93
+ const lookup = child.lookup(path, info)
94
+
95
+ if (lookup !== null) {
96
+ return lookup
97
+ }
58
98
  }
59
99
 
60
- if (this.placeholder) {
61
- info.params[this.placeholder.name] = current;
62
- return this.placeholder.lookup(rest, info);
100
+ // This will return the first placeholder that matches early
101
+ for (const placeholder of this.placeholders) {
102
+ const result = placeholder.lookup(path, info)
103
+
104
+ if (result !== null) {
105
+ result.params[placeholder.name] = current
106
+ return result
107
+ }
63
108
  }
64
109
 
65
- return null;
110
+ return null
66
111
  }
67
112
  }
68
113
 
69
- export class Router<T> {
70
- root: RouteNode<T> = new RouteNode<T>('');
114
+ export class Router<T = any> {
115
+ constructor(public root: RouteNode<T> = new RouteNode<T>('', 0)) {}
116
+
117
+ getPath(path: string) {
118
+ return stripSlashes(path).split('/')
119
+ }
71
120
 
72
- getPath (path: string) {
73
- return stripSlashes(path).split('/');
121
+ insert(path: string, data: T) {
122
+ return this.root.insert(this.getPath(path), data)
74
123
  }
75
124
 
76
- insert (path: string, data: T) {
77
- return this.root.insert(this.getPath(path), data);
125
+ remove(path: string) {
126
+ return this.root.remove(this.getPath(path))
78
127
  }
79
128
 
80
- lookup (path: string) {
129
+ lookup(path: string) {
81
130
  if (typeof path !== 'string') {
82
- return null;
131
+ return null
83
132
  }
84
133
 
85
- return this.root.lookup(this.getPath(path), { params: {} });
134
+ return this.root.lookup(this.getPath(path), { params: {} })
86
135
  }
87
136
  }
@@ -1,70 +1,70 @@
1
- import { Application, getServiceOptions, Params } from '@feathersjs/feathers';
2
- import { createDebug } from '@feathersjs/commons';
3
- import { channels } from '../channels';
4
- import { routing } from '../routing';
5
- import { getDispatcher, runMethod } from './utils';
6
- import { RealTimeConnection } from '../channels/channel/base';
1
+ import { Application, getServiceOptions, Params } from '@feathersjs/feathers'
2
+ import { createDebug } from '@feathersjs/commons'
3
+ import { channels } from '../channels'
4
+ import { routing } from '../routing'
5
+ import { getDispatcher, runMethod } from './utils'
6
+ import { RealTimeConnection } from '../channels/channel/base'
7
7
 
8
- const debug = createDebug('@feathersjs/transport-commons');
8
+ const debug = createDebug('@feathersjs/transport-commons')
9
9
 
10
10
  export interface SocketOptions {
11
- done: Promise<any>;
12
- emit: string;
13
- socketMap: WeakMap<RealTimeConnection, any>;
14
- socketKey?: any;
15
- getParams: (socket: any) => RealTimeConnection;
11
+ done: Promise<any>
12
+ emit: string
13
+ socketMap: WeakMap<RealTimeConnection, any>
14
+ socketKey?: any
15
+ getParams: (socket: any) => RealTimeConnection
16
16
  }
17
17
 
18
- export function socket ({ done, emit, socketMap, socketKey, getParams }: SocketOptions) {
18
+ export function socket({ done, emit, socketMap, socketKey, getParams }: SocketOptions) {
19
19
  return (app: Application) => {
20
20
  const leaveChannels = (connection: RealTimeConnection) => {
21
- const { channels } = app;
21
+ const { channels } = app
22
22
 
23
23
  if (channels.length) {
24
- app.channel(app.channels).leave(connection);
24
+ app.channel(app.channels).leave(connection)
25
25
  }
26
- };
26
+ }
27
27
 
28
- app.configure(channels());
29
- app.configure(routing());
28
+ app.configure(channels())
29
+ app.configure(routing())
30
30
 
31
- app.on('publish', getDispatcher(emit, socketMap, socketKey));
32
- app.on('disconnect', leaveChannels);
31
+ app.on('publish', getDispatcher(emit, socketMap, socketKey))
32
+ app.on('disconnect', leaveChannels)
33
33
  app.on('logout', (_authResult: any, params: Params) => {
34
- const { connection } = params;
34
+ const { connection } = params
35
35
 
36
36
  if (connection) {
37
- leaveChannels(connection);
37
+ leaveChannels(connection)
38
38
  }
39
- });
39
+ })
40
40
 
41
41
  // `connection` event
42
- done.then(provider => provider.on('connection', (connection: any) =>
43
- app.emit('connection', getParams(connection)))
44
- );
42
+ done.then((provider) =>
43
+ provider.on('connection', (connection: any) => app.emit('connection', getParams(connection)))
44
+ )
45
45
 
46
46
  // `socket.emit('methodName', 'serviceName', ...args)` handlers
47
- done.then(provider => provider.on('connection', (connection: any) => {
48
- const methodHandlers = Object.keys(app.services).reduce((result, name) => {
49
- const { methods } = getServiceOptions(app.service(name));
47
+ done.then((provider) =>
48
+ provider.on('connection', (connection: any) => {
49
+ const methodHandlers = Object.keys(app.services).reduce((result, name) => {
50
+ const { methods } = getServiceOptions(app.service(name))
50
51
 
51
- methods.forEach(method => {
52
- if (!result[method]) {
53
- result[method] = (...args: any[]) => {
54
- const path = args.shift();
52
+ methods.forEach((method) => {
53
+ if (!result[method]) {
54
+ result[method] = (...args: any[]) => {
55
+ const path = args.shift()
55
56
 
56
- debug(`Got '${method}' call for service '${path}'`);
57
- runMethod(app, getParams(connection), path, method, args);
57
+ debug(`Got '${method}' call for service '${path}'`)
58
+ runMethod(app, getParams(connection), path, method, args)
59
+ }
58
60
  }
59
- }
60
- });
61
+ })
61
62
 
62
- return result;
63
- }, {} as any);
63
+ return result
64
+ }, {} as any)
64
65
 
65
- Object.keys(methodHandlers).forEach(key =>
66
- connection.on(key, methodHandlers[key])
67
- );
68
- }));
69
- };
66
+ Object.keys(methodHandlers).forEach((key) => connection.on(key, methodHandlers[key]))
67
+ })
68
+ )
69
+ }
70
70
  }