Browse Source

add feathers framework (#7125)

Joshua Cox 3 years ago
parent
commit
4281c9eea3
27 changed files with 783 additions and 0 deletions
  1. 40 0
      frameworks/TypeScript/feathersjs/README.md
  2. 30 0
      frameworks/TypeScript/feathersjs/benchmark_config.json
  3. 10 0
      frameworks/TypeScript/feathersjs/config/default.json
  4. 13 0
      frameworks/TypeScript/feathersjs/feathersjs.dockerfile
  5. 63 0
      frameworks/TypeScript/feathersjs/package.json
  6. 34 0
      frameworks/TypeScript/feathersjs/src/app.hooks.ts
  7. 64 0
      frameworks/TypeScript/feathersjs/src/app.ts
  8. 65 0
      frameworks/TypeScript/feathersjs/src/channels.ts
  9. 6 0
      frameworks/TypeScript/feathersjs/src/declarations.d.ts
  10. 30 0
      frameworks/TypeScript/feathersjs/src/index.ts
  11. 16 0
      frameworks/TypeScript/feathersjs/src/logger.ts
  12. 6 0
      frameworks/TypeScript/feathersjs/src/middleware/index.ts
  13. 33 0
      frameworks/TypeScript/feathersjs/src/models/fortune.model.ts
  14. 33 0
      frameworks/TypeScript/feathersjs/src/models/world.model.ts
  15. 35 0
      frameworks/TypeScript/feathersjs/src/sequelize.ts
  16. 23 0
      frameworks/TypeScript/feathersjs/src/services/fortune/fortune.class.ts
  17. 33 0
      frameworks/TypeScript/feathersjs/src/services/fortune/fortune.hooks.ts
  18. 33 0
      frameworks/TypeScript/feathersjs/src/services/fortune/fortune.service.ts
  19. 18 0
      frameworks/TypeScript/feathersjs/src/services/index.ts
  20. 17 0
      frameworks/TypeScript/feathersjs/src/services/json/json.service.ts
  21. 18 0
      frameworks/TypeScript/feathersjs/src/services/plaintext/plaintext.service.ts
  22. 42 0
      frameworks/TypeScript/feathersjs/src/services/world/world.class.ts
  23. 42 0
      frameworks/TypeScript/feathersjs/src/services/world/world.hooks.ts
  24. 50 0
      frameworks/TypeScript/feathersjs/src/services/world/world.service.ts
  25. 3 0
      frameworks/TypeScript/feathersjs/src/util.ts
  26. 13 0
      frameworks/TypeScript/feathersjs/tsconfig.json
  27. 13 0
      frameworks/TypeScript/feathersjs/views/fortunes.pug

+ 40 - 0
frameworks/TypeScript/feathersjs/README.md

@@ -0,0 +1,40 @@
+# FeathersJS Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](src/services/json/json.service.ts)
+* [PLAINTEXT](src/services/plaintext/plaintext.service.ts)
+* [DB](src/services/world/world.service.ts)
+* [QUERY](src/services/world/world.service.ts)
+* [UPDATE](src/services/world/world.service.ts)
+* [FORTUNES](src/services/fortune/fortune.service.ts)
+
+## Important Libraries
+The tests were run with:
+* [Feathers](https://github.com/feathersjs/feathers)
+* [Feathers-Sequelize](https://github.com/feathersjs-ecosystem/feathers-sequelize)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/query?queries=
+
+### UPDATE
+
+http://localhost:8080/update?queries=
+
+### FORTUNES
+
+http://localhost:8080/fortunes

+ 30 - 0
frameworks/TypeScript/feathersjs/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+  "framework": "feathersjs",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/update?queries=",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "postgres",
+        "framework": "FeathersJS",
+        "language": "TypeScript",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "FeathersJS",
+        "notes": "",
+        "versus": "nodejs"
+      }
+    }
+  ]
+}

+ 10 - 0
frameworks/TypeScript/feathersjs/config/default.json

@@ -0,0 +1,10 @@
+{
+  "host": "localhost",
+  "port": 8080,
+  "public": "../public/",
+  "paginate": {
+    "default": 10,
+    "max": 50
+  },
+  "postgres": "postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world"
+}

+ 13 - 0
frameworks/TypeScript/feathersjs/feathersjs.dockerfile

@@ -0,0 +1,13 @@
+FROM node:16
+
+COPY ./ ./
+
+ENV NODE_ENV development
+
+RUN npm install
+
+ENV DATABASE_CONFIGURATION_PROFILE postgres
+ENV FRAMEWORK express
+
+EXPOSE 8080
+CMD ["npm", "run", "start"]

+ 63 - 0
frameworks/TypeScript/feathersjs/package.json

@@ -0,0 +1,63 @@
+{
+  "name": "feathers",
+  "description": "FeathersJS App",
+  "version": "0.0.0",
+  "main": "src",
+  "author": "",
+  "directories": {
+    "lib": "src",
+    "test": "test/",
+    "config": "config/"
+  },
+  "engines": {
+    "node": "^16.0.0",
+    "npm": ">= 8.0.0"
+  },
+  "scripts": {
+    "test": "npm run lint && npm run compile && npm run jest",
+    "lint": "eslint src/. test/. --config .eslintrc.json --ext .ts --fix",
+    "dev": "ts-node-dev --no-notify src/",
+    "start": "ts-node src/index",
+    "jest": "jest --forceExit",
+    "compile": "shx rm -rf lib/ && tsc"
+  },
+  "types": "lib/",
+  "dependencies": {
+    "@feathersjs/configuration": "^4.5.12",
+    "@feathersjs/errors": "^4.5.12",
+    "@feathersjs/express": "^4.5.12",
+    "@feathersjs/feathers": "^4.5.12",
+    "@feathersjs/transport-commons": "^4.5.12",
+    "@types/express": "^4.17.13",
+    "compression": "^1.7.4",
+    "cors": "^2.8.5",
+    "feathers-memory": "^4.1.0",
+    "feathers-sequelize": "^6.3.2",
+    "helmet": "^4.6.0",
+    "mysql2": "^2.3.3",
+    "pg": "^8.7.3",
+    "pg-hstore": "^2.3.4",
+    "pug": "^3.0.2",
+    "sequelize": "^6.15.1",
+    "serve-favicon": "^2.5.0",
+    "ts-node": "^7.0.1",
+    "typescript": "^4.5.5",
+    "winston": "^3.5.1"
+  },
+  "devDependencies": {
+    "@types/bluebird": "^3.5.36",
+    "@types/compression": "^1.7.2",
+    "@types/cors": "^2.8.12",
+    "@types/jest": "^27.0.2",
+    "@types/serve-favicon": "^2.5.3",
+    "@types/validator": "^10.11.3",
+    "@typescript-eslint/eslint-plugin": "^5.10.2",
+    "@typescript-eslint/parser": "^5.10.2",
+    "axios": "^0.25.0",
+    "eslint": "^8.8.0",
+    "jest": "^27.3.1",
+    "shx": "^0.3.4",
+    "ts-jest": "^27.1.3",
+    "ts-node-dev": "^1.1.8"
+  }
+}

+ 34 - 0
frameworks/TypeScript/feathersjs/src/app.hooks.ts

@@ -0,0 +1,34 @@
+// Application hooks that run for every service
+// Don't remove this comment. It's needed to format import lines nicely.
+
+export default {
+  before: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  after: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  error: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  }
+};

+ 64 - 0
frameworks/TypeScript/feathersjs/src/app.ts

@@ -0,0 +1,64 @@
+import path from 'path';
+import favicon from 'serve-favicon';
+import compress from 'compression';
+import helmet from 'helmet';
+import cors from 'cors';
+import { Request, Response, NextFunction } from 'express';
+
+import feathers from '@feathersjs/feathers';
+import configuration from '@feathersjs/configuration';
+import express from '@feathersjs/express';
+
+
+
+import { Application } from './declarations';
+import logger from './logger';
+import middleware from './middleware';
+import services from './services';
+import appHooks from './app.hooks';
+import channels from './channels';
+import { HookContext as FeathersHookContext } from '@feathersjs/feathers';
+import sequelize from './sequelize';
+// Don't remove this comment. It's needed to format import lines nicely.
+
+const app: Application = express(feathers());
+export type HookContext<T = any> = { app: Application } & FeathersHookContext<T>;
+
+app.set('view engine', 'pug');
+
+// Load app configuration
+app.configure(configuration());
+// Enable security, CORS, compression, favicon and body parsing
+app.use(helmet({
+  contentSecurityPolicy: false
+}));
+app.use(cors());
+app.use(compress());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+app.use((req: Request, res: Response, next: NextFunction) => {
+  res.setHeader('Server', 'FeathersJS');
+  next();
+});
+
+// Set up Plugins and providers
+app.configure(express.rest());
+
+
+app.configure(sequelize);
+
+
+// Configure other middleware (see `middleware/index.ts`)
+app.configure(middleware);
+// Set up our services (see `services/index.ts`)
+app.configure(services);
+// Set up event channels (see channels.ts)
+app.configure(channels);
+
+// Configure a middleware for 404s and the error handler
+app.use(express.notFound());
+app.use(express.errorHandler({ logger } as any));
+
+app.hooks(appHooks);
+
+export default app;

+ 65 - 0
frameworks/TypeScript/feathersjs/src/channels.ts

@@ -0,0 +1,65 @@
+import '@feathersjs/transport-commons';
+import { HookContext } from '@feathersjs/feathers';
+import { Application } from './declarations';
+
+export default function(app: Application): void {
+  if(typeof app.channel !== 'function') {
+    // If no real-time functionality has been configured just return
+    return;
+  }
+
+  app.on('connection', (connection: any): void => {
+    // On a new real-time connection, add it to the anonymous channel
+    app.channel('anonymous').join(connection);
+  });
+
+  app.on('login', (authResult: any, { connection }: any): void => {
+    // connection can be undefined if there is no
+    // real-time connection, e.g. when logging in via REST
+    if(connection) {
+      // Obtain the logged in user from the connection
+      // const user = connection.user;
+      
+      // The connection is no longer anonymous, remove it
+      app.channel('anonymous').leave(connection);
+
+      // Add it to the authenticated user channel
+      app.channel('authenticated').join(connection);
+
+      // Channels can be named anything and joined on any condition 
+      
+      // E.g. to send real-time events only to admins use
+      // if(user.isAdmin) { app.channel('admins').join(connection); }
+
+      // If the user has joined e.g. chat rooms
+      // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection));
+      
+      // Easily organize users by email and userid for things like messaging
+      // app.channel(`emails/${user.email}`).join(connection);
+      // app.channel(`userIds/${user.id}`).join(connection);
+    }
+  });
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  app.publish((data: any, hook: HookContext) => {
+    // Here you can add event publishers to channels set up in `channels.ts`
+    // To publish only for a specific event use `app.publish(eventname, () => {})`
+
+    console.log('Publishing all events to all authenticated users. See `channels.ts` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line
+
+    // e.g. to publish all service events to all authenticated users use
+    return app.channel('authenticated');
+  });
+
+  // Here you can also add service specific event publishers
+  // e.g. the publish the `users` service `created` event to the `admins` channel
+  // app.service('users').publish('created', () => app.channel('admins'));
+  
+  // With the userid and email organization from above you can easily select involved users
+  // app.service('messages').publish(() => {
+  //   return [
+  //     app.channel(`userIds/${data.createdBy}`),
+  //     app.channel(`emails/${data.recipientEmail}`)
+  //   ];
+  // });
+}

+ 6 - 0
frameworks/TypeScript/feathersjs/src/declarations.d.ts

@@ -0,0 +1,6 @@
+import { Application as ExpressFeathers } from '@feathersjs/express';
+
+// A mapping of service names to types. Will be extended in service files.
+export interface ServiceTypes {}
+// The application instance type that will be used everywhere else
+export type Application = ExpressFeathers<ServiceTypes>;

+ 30 - 0
frameworks/TypeScript/feathersjs/src/index.ts

@@ -0,0 +1,30 @@
+import cluster from 'cluster';
+import os from 'os';
+
+import logger from './logger';
+import app from './app';
+
+if (cluster.isPrimary) {
+  const cpuCount: os.CpuInfo[] = os.cpus();
+
+  for (const cpu of cpuCount) {
+    cluster.fork();
+  }
+
+  cluster.on('exit', () => {
+    process.exit(1);
+  });
+} else {
+
+  const port = app.get('port');
+  const server = app.listen(port);
+
+  process.on('unhandledRejection', (reason, p) =>
+    logger.error('Unhandled Rejection at: Promise ', p, reason)
+  );
+
+  server.on('listening', () =>
+    logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
+  );
+}
+

+ 16 - 0
frameworks/TypeScript/feathersjs/src/logger.ts

@@ -0,0 +1,16 @@
+import { createLogger, format, transports } from 'winston';
+
+// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston
+const logger = createLogger({
+  // To see more detailed errors, change this to 'debug'
+  level: 'info',
+  format: format.combine(
+    format.splat(),
+    format.simple()
+  ),
+  transports: [
+    new transports.Console()
+  ],
+});
+
+export default logger;

+ 6 - 0
frameworks/TypeScript/feathersjs/src/middleware/index.ts

@@ -0,0 +1,6 @@
+import { Application } from '../declarations';
+// Don't remove this comment. It's needed to format import lines nicely.
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
+export default function (app: Application): void {
+}

+ 33 - 0
frameworks/TypeScript/feathersjs/src/models/fortune.model.ts

@@ -0,0 +1,33 @@
+// See https://sequelize.org/master/manual/model-basics.html
+// for more of what you can do here.
+import { Sequelize, DataTypes, Model } from 'sequelize';
+import { Application } from '../declarations';
+
+export interface FortuneModel {
+  id: number
+  message: string
+}
+
+export default function (app: Application): typeof Model {
+  const sequelizeClient: Sequelize = app.get('sequelizeClient');
+  const fortune = sequelizeClient.define('fortune', {
+    message: {
+      type: DataTypes.STRING,
+      allowNull: false
+    }
+  }, {
+    hooks: {
+      beforeCount(options: any): any {
+        options.raw = true;
+      }
+    }
+  });
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  (fortune as any).associate = function (models: any): void {
+    // Define associations here
+    // See https://sequelize.org/master/manual/assocs.html
+  };
+
+  return fortune;
+}

+ 33 - 0
frameworks/TypeScript/feathersjs/src/models/world.model.ts

@@ -0,0 +1,33 @@
+// See https://sequelize.org/master/manual/model-basics.html
+// for more of what you can do here.
+import { Sequelize, DataTypes, Model } from 'sequelize';
+import { Application } from '../declarations';
+
+export interface WorldModel {
+  id: number
+  randomnumber: number
+}
+
+export default function (app: Application): typeof Model {
+  const sequelizeClient: Sequelize = app.get('sequelizeClient');
+  const world = sequelizeClient.define('world', {
+    randomnumber: {
+      type: DataTypes.INTEGER,
+      allowNull: false
+    }
+  }, {
+    hooks: {
+      beforeCount(options: any): any {
+        options.raw = true;
+      }
+    }
+  });
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  (world as any).associate = function (models: any): void {
+    // Define associations here
+    // See https://sequelize.org/master/manual/assocs.html
+  };
+
+  return world;
+}

+ 35 - 0
frameworks/TypeScript/feathersjs/src/sequelize.ts

@@ -0,0 +1,35 @@
+import { Sequelize } from 'sequelize';
+import { Application } from './declarations';
+import { randInt } from './util';
+
+export default function (app: Application): void {
+  const connectionString = app.get('postgres');
+  const sequelize = new Sequelize(connectionString, {
+    dialect: 'postgres',
+    logging: false,
+    define: {
+      freezeTableName: true,
+      timestamps: false
+    }
+  });
+  const oldSetup = app.setup;
+
+  app.set('sequelizeClient', sequelize);
+
+  app.setup = function (...args): Application {
+    const result = oldSetup.apply(this, args);
+
+    // Set up data relationships
+    const models = sequelize.models;
+    Object.keys(models).forEach(name => {
+      if ('associate' in models[name]) {
+        (models[name] as any).associate(models);
+      }
+    });
+
+    // Sync to the database
+    app.set('sequelizeSync', sequelize.sync());
+
+    return result;
+  };
+}

+ 23 - 0
frameworks/TypeScript/feathersjs/src/services/fortune/fortune.class.ts

@@ -0,0 +1,23 @@
+import { Service, SequelizeServiceOptions } from 'feathers-sequelize';
+import { Application } from '../../declarations';
+import { FortuneModel } from '../../models/fortune.model';
+
+export class Fortune extends Service {
+  //eslint-disable-next-line @typescript-eslint/no-unused-vars
+  constructor(options: Partial<SequelizeServiceOptions>, app: Application) {
+    super(options);
+  }
+
+  async getFortunes() {
+    const fortunes = <FortuneModel[]> await this.find();
+
+    fortunes.push({
+      id: 0,
+      message: 'Additional fortune added at request time.',
+    });
+
+    fortunes.sort((f, s) => (f.message < s.message ? -1 : 1));
+
+    return fortunes;
+  }
+}

+ 33 - 0
frameworks/TypeScript/feathersjs/src/services/fortune/fortune.hooks.ts

@@ -0,0 +1,33 @@
+import { HooksObject } from '@feathersjs/feathers';
+
+export default {
+  before: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  after: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  error: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  }
+};

+ 33 - 0
frameworks/TypeScript/feathersjs/src/services/fortune/fortune.service.ts

@@ -0,0 +1,33 @@
+// Initializes the `fortune` service on path `/fortune`
+import { ServiceAddons } from '@feathersjs/feathers';
+import { Application } from '../../declarations';
+import { Request, Response } from 'express';
+import { Fortune } from './fortune.class';
+import createModel from '../../models/fortune.model';
+import hooks from './fortune.hooks';
+
+// Add this service to the service type index
+declare module '../../declarations' {
+  interface ServiceTypes {
+    'fortune': Fortune & ServiceAddons<any>;
+  }
+}
+
+export default function (app: Application): void {
+  const options = {
+    Model: createModel(app)
+  };
+
+  // Initialize our service with any options it requires
+  app.use('/fortune', new Fortune(options, app));
+
+  app.get('/fortunes', async (req: Request, res: Response) => {
+    const fortunes = await app.service('fortune').getFortunes();
+    res.render('fortunes', { fortunes });
+  });
+
+  // Get our initialized service so that we can register hooks
+  const service = app.service('fortune');
+
+  service.hooks(hooks);
+}

+ 18 - 0
frameworks/TypeScript/feathersjs/src/services/index.ts

@@ -0,0 +1,18 @@
+import { Application } from '../declarations';
+
+import plaintext from './plaintext/plaintext.service';
+
+import json from './json/json.service';
+
+import fortune from './fortune/fortune.service';
+
+import world from './world/world.service';
+
+// Don't remove this comment. It's needed to format import lines nicely.
+
+export default function (app: Application): void {
+  app.configure(plaintext);
+  app.configure(json);
+  app.configure(fortune);
+  app.configure(world);
+}

+ 17 - 0
frameworks/TypeScript/feathersjs/src/services/json/json.service.ts

@@ -0,0 +1,17 @@
+// Initializes the `json` service on path `/json`
+import { ServiceAddons } from '@feathersjs/feathers';
+import { Application } from '../../declarations';
+import { Request, Response } from 'express';
+
+// Add this service to the service type index
+declare module '../../declarations' {
+  interface ServiceTypes {
+    'json': ServiceAddons<any>;
+  }
+}
+
+export default function (app: Application): void {
+  app.get('/json', (req: Request, res: Response) => {
+    res.json({ message: 'Hello, World!' });
+  });
+}

+ 18 - 0
frameworks/TypeScript/feathersjs/src/services/plaintext/plaintext.service.ts

@@ -0,0 +1,18 @@
+// Initializes the `plaintext` service on path `/plaintext`
+import { ServiceAddons } from '@feathersjs/feathers';
+import { Application } from '../../declarations';
+import { Request, Response } from 'express';
+
+// Add this service to the service type index
+declare module '../../declarations' {
+  interface ServiceTypes {
+    'plaintext': ServiceAddons<any>;
+  }
+}
+
+export default function (app: Application): void {
+  app.get('/plaintext', (req: Request, res: Response) => {
+    res.setHeader('Content-Type', 'text/plain');
+    res.send('Hello, World!');
+  });
+}

+ 42 - 0
frameworks/TypeScript/feathersjs/src/services/world/world.class.ts

@@ -0,0 +1,42 @@
+import { Service, SequelizeServiceOptions } from 'feathers-sequelize';
+import { Application } from '../../declarations';
+import { WorldModel } from '../../models/world.model';
+import { randInt } from '../../util';
+
+export class World extends Service {
+  //eslint-disable-next-line @typescript-eslint/no-unused-vars
+  constructor(options: Partial<SequelizeServiceOptions>, app: Application) {
+    super(options);
+  }
+
+  async findRandom(): Promise<WorldModel> {
+    return this.get(randInt());
+  }
+
+  async findMultiple(count: number): Promise<WorldModel[]> {
+    const worldPromises: Promise<WorldModel>[] = [];
+
+    for (let i = 0 ; i < count ; i++) {
+      worldPromises.push(this.findRandom());
+    }
+
+    return await Promise.all(worldPromises);
+  }
+
+  async updateMultiple(count: number): Promise<WorldModel[]> {
+    const worlds: WorldModel[] = [];
+
+    for (let i = 0; i < count; i++) {
+      const world = await this.findRandom();
+      world.randomnumber = randInt();
+      worlds.push(world);
+      this.Model.update({ randomnumber: world.randomnumber }, {
+        where: {
+          id: world.id
+        }
+      });
+    }
+
+    return await Promise.all(worlds);
+  }
+}

+ 42 - 0
frameworks/TypeScript/feathersjs/src/services/world/world.hooks.ts

@@ -0,0 +1,42 @@
+import { HookContext } from '@feathersjs/feathers';
+import { hooks } from 'feathers-sequelize';
+
+const { hydrate } = hooks; 
+
+function rawFalse(context: HookContext) {
+  if (!context.params.sequelize) context.params.sequelize = {};
+  Object.assign(context.params.sequelize, { raw: false });
+  return context;
+}
+
+export default {
+  before: {
+    all: [rawFalse],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  after: {
+    all: [hydrate()],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  },
+
+  error: {
+    all: [],
+    find: [],
+    get: [],
+    create: [],
+    update: [],
+    patch: [],
+    remove: []
+  }
+};

+ 50 - 0
frameworks/TypeScript/feathersjs/src/services/world/world.service.ts

@@ -0,0 +1,50 @@
+// Initializes the `world` service on path `/world`
+import { Params, ServiceAddons } from '@feathersjs/feathers';
+import { Application } from '../../declarations';
+import { Request, Response } from 'express';
+import { World } from './world.class';
+import createModel from '../../models/world.model';
+import hooks from './world.hooks';
+import { randInt } from '../../util';
+
+// Add this service to the service type index
+declare module '../../declarations' {
+  interface ServiceTypes {
+    'world': World & ServiceAddons<any>;
+  }
+}
+
+export default function (app: Application): void {
+  const options = {
+    Model: createModel(app),
+    paginate: app.get('paginate')
+  };
+
+  // Initialize our service with any options it requires
+  app.use('/world', new World(options, app));
+
+  app.get('/db', async (req: Request, res: Response) => {
+    const world = await app.service('world').findRandom();
+
+    res.json(world);
+  });
+
+  app.get('/queries', async (req: Request, res: Response) => {
+    const queries = Math.min(Math.max(parseInt(<string>req.query.queries) || 1, 1), 500);
+    const worlds = await app.service('world').findMultiple(queries);
+    
+    res.json(worlds);
+  });
+
+  app.get('/update', async (req: Request, res: Response) => {
+    const queries = Math.min(Math.max(parseInt(<string>req.query.queries) || 1, 1), 500);
+    const worlds = await app.service('world').updateMultiple(queries);
+    
+    res.json(worlds);
+  });
+
+  // Get our initialized service so that we can register hooks
+  const service = app.service('world');
+
+  service.hooks(hooks);
+}

+ 3 - 0
frameworks/TypeScript/feathersjs/src/util.ts

@@ -0,0 +1,3 @@
+export const randInt = () => {
+  return Math.floor(Math.random() * 10000) + 1;
+};

+ 13 - 0
frameworks/TypeScript/feathersjs/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "target": "es2018",
+    "module": "commonjs",
+    "outDir": "./lib",
+    "rootDir": "./src",
+    "strict": true,
+    "esModuleInterop": true
+  },
+  "exclude": [
+    "test"
+  ]
+}

+ 13 - 0
frameworks/TypeScript/feathersjs/views/fortunes.pug

@@ -0,0 +1,13 @@
+doctype html
+html
+  head
+    title Fortunes
+  body
+    table
+      tr
+        th id
+        th message
+      each fortune in fortunes
+        tr
+          td #{fortune.id}
+          td #{fortune.message}