Coder Social home page Coder Social logo

ditojs / dito Goto Github PK

View Code? Open in Web Editor NEW
81.0 4.0 7.0 1.09 GB

Dito.js is a declarative and modern web framework with a focus on API driven development, based on Objection.js, Koa.js and Vue.js – Developed at Lineto by Jürg Lehni and made available by Lineto in 2018 under the MIT license

Home Page: https://lineto.com

License: MIT License

JavaScript 68.13% Vue 29.14% Shell 0.01% SCSS 2.72%
api server json rest objection vue koa sql declarative

dito's Introduction

Dito.js

Dito.js is a declarative and modern web framework with a focus on API driven development, based on Koa.js, Objection.js and Vue.js

Dito.js consists of two main components: Dito.js Server, providing all classes and methods to build the server architecture, and Dito.js Admin, a Vue.js library that can be used to build views and forms for administration of the Dito.js models in a very efficient fashion.

Both components share the key philosophy of the Dito.js framework: The use of declarative descriptions not only of the data structures themselves (the models), but also of the way they are remotely interacted with (the controllers), as well as the way they are edited (the admin views and forms).

This is then also the reason for the name Dito.js itself:

Ditto originally comes from the Latin word dictus, "having been said," the past participle of the verb dīcere, "to say." https://www.thefreedictionary.com/ditto

Dito.js was developed at Lineto by Jürg Lehni, and was made available by Lineto in 2018 under the MIT license.

Structuring a Dito.js Application

Unlike other frameworks, Dito.js is not opinionated about its folder structures and file naming, and does not deduce any information from such structures. With the exception of the creation of migration files, there aren't any generators that automatically create files for you.

There is however a recommended way to structure a Dito.js application, by dividing it into the following folder structure:

  • src/server: This folder contains the admin Dito.js Server app, along with all models and controllers in sub-folders:
    • src/server/models: The place where for the model classes.
    • src/server/controllers: The place for the controller classes.
  • src/admin: This folder contains a declarations of all admin views and forms
  • src/config: The application configuration files.
  • migrations: The folder holding all migration files.
  • seeds: The folder holding all seeds files.

This structure will be explained in more detail in the documentation of each these aspects separately:

Setting up package.json for a Dito.js Application

Dito.js server comes with its own CLI program, but it is rare to call it directly: Normally you simply set up a set of package.json scripts through which a selection of predefined tasks are executed:

"scripts": {
  "console": "dito console src/server/app",
  "db:seed": "dito db:seed src/server/app",
  "db:create_migration": "dito db:create_migration src/server/app",
  "db:migrate": "dito db:migrate src/config/index.js",
  "db:rollback": "dito db:rollback src/config/index.js",
  "db:reset": "dito db:reset src/config/index.js"
}

Note that in order to work, each of these scripts require either the path to the application, or the path to the application's configuration, as specified above. Here a brief description of each script's purpose:

  • yarn console: Starts an interactive Read-Eval-Print-Loop console in which all Dito.js models can be directly used.
  • yarn db:seed: Seeds the configured database with the data provided in seeds. See Seeds for more information.
  • yarn db:create_migration: Creates migration files for the specified models. See Migrations for more information.
  • yarn db:migrate: Migrates to the latest state of migrations. See Migrations for more information.
  • yarn db:rollback: Rolls back the last batch of applied migrations. See Migrations for more information.
  • yarn db:reset: Resets the database by rolling back all applied migrations, and then reapplying all available migrations. See Migrations for more information.

dito's People

Contributors

dependabot[bot] avatar fpatrik avatar koskimas avatar lehni avatar ollisal avatar ptoussai avatar puckey avatar rivertam avatar wirsing avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dito's Issues

Logo design

Hi. I am a graphic designer. I volunteer to design a logo for open source projects. I can design it for you to use it in the readme file. What dou you say?

Add support for transactions on controller actions

As proposed by @koskimas & @ollisal:

@action('get')
@transactional()
async doStuff(ctx) {
  await Thingy.query(ctx.trx)
  ...
}

The koa's middleware are perfect for transaction management:

async function transactionMiddleware(ctx, next) {
  const trx = ctx.trx = await transaction.start()
  try {
    await next()
    await trx.commit()
  } catch (err) {
    await trx.rollback()
  }
  ctx.trx = null
}

This would allow other middleware to use the transaction too. For example it's sometimes good (or even necessary) that the session update query takes part in the same transaction. This way the session middleware could use ctx.trx if it exists.

Fix logger typings

the logger is now set up like this:

  setupLogger() {
    const { prettyPrint, ...options } = this.config.logger
    const transport = prettyPrint
      ? pino.transport({
          target: 'pino-pretty',
          options: prettyPrint
        })
      : null
    this.logger = pino(options, transport).child({ name: 'app' })
  }

We don't use koa-pino-logger or koa-logger anymore, jsut pino and pino-pretty, and app.config.logger gets split into { prettyPrint, ...options }, of which prettyPrint are the pino-pretty options, and …options are the pino options.

This should be reflected in the server typings, but I don't know enough to figure out how to do this.

@puckey could you take care of it? Thank you!

Migrations always contain table.string('#id') and table.string('#ref')

When creating migrations, the tables always include #id and #ref columns:

export async function up(knex) {
  await knex.schema
    .createTable('channel', table => {
      table.increments('id').primary()
      table.string('#id')
      table.string('#ref')
    })
}

export async function down(knex) {
  await knex.schema
    .dropTableIfExists('channel')
}

TypeError: Cannot read property 'messages' of null

I was able to reproduce the issue I mentioned in ab6e4e4 here: https://github.com/puckey/dito-example/tree/reproduce/tab-error

Go to /admin/dummies/1 and then click on the edit button of one of the messages.

it produces the following error:

vue.esm.js:628 [Vue warn]: Error in render: "TypeError: Cannot read property 'messages' of null"

found in

---> <DitoForm>... (1 recursive calls)
       <DitoView>
         <DitoRoot>
           <Root>
warn @ vue.esm.js:628

vue.esm.js:1897 TypeError: Cannot read property 'messages' of null
    at VueComponent.selectedTab (DitoSchema.vue:247)
    at Watcher.get (vue.esm.js:4488)
    at Watcher.evaluate (vue.esm.js:4593)
    at VueComponent.computedGetter [as selectedTab] (vue.esm.js:4845)
    at VueComponent.selectedTab (DitoForm.vue:129)
    at Watcher.get (vue.esm.js:4488)
    at Watcher.evaluate (vue.esm.js:4593)
    at Proxy.computedGetter (vue.esm.js:4845)
    at Proxy.pi (DitoForm.vue?3047:1)
    at VueComponent.Vue._render (vue.esm.js:3557)

defaults in app configuration

  • env could default to process.env.NODE_ENV when not supplied
  • server.host could default to env.NODE_HOST || env.HOST || '0.0.0.0' when not supplied
  • server.port could default to env.NODE_PORT || env.PORT || 8080 when not supplied
  • log and server could be made optional like app is

Fix errors in server typings

We have fixed most server typings, but one problem remains.

Running yarn types in the server package prints:

➜  server git:(master) ✗ yarn types
types/index.d.ts:1838:33 - error TS2536: Type '$Key' cannot be used to index type 'T'.

1838   [$Key in SelectModelKeys<T>]: T[$Key] extends Model
                                     ~~~~~~~

types/index.d.ts:1839:29 - error TS2536: Type '$Key' cannot be used to index type 'T'.

1839     ? SelectModelProperties<T[$Key]>
                                 ~~~~~~~

types/index.d.ts:1840:7 - error TS2536: Type '$Key' cannot be used to index type 'T'.

1840     : T[$Key]
           ~~~~~~~

This is all due to the following types:

export type SelectModelProperties<T> = {
  [$Key in SelectModelKeys<T>]: T[$Key] extends Model
    ? SelectModelProperties<T[$Key]>
    : T[$Key]
}

export type SelectModelKeys<T> = AnyGate<
  T,
  Exclude<
    keyof ConditionalExcept<T, Function>,
    |  `$${string}`
    | 'QueryBuilderType'
    | 'foreignKeyId'
  >,
  string
>

What I find interesting is that this all looks quite similar to what Objection.js does with ModelObject and DataPropertyNames.

I have played around with these and have managed to make the errors go away, but I am not certain if it's right:

type SelectModelKeys<T> = Exclude<
  objection.NonFunctionPropertyNames<T>,
  | `$${string}`
  | 'QueryBuilderType'
  | 'foreignKeyId'
>

@puckey could you take a look?

isUrl fails for long tlds

We noticed that the isUrl utility function was failing for tlds longer than 6 characters. Added two failing tests to the following PR to demonstrate: 934e4af

clone() does not work with regular expression vaules.

This should do the trick:

function clone(val, iteratee = null) {
  let copy
  if (isDate(val)) {
    copy = new val.constructor(+val)
  } else if (isRegExp(val)) {
    copy = new RegExp(val)
  } else if (isObject(val)) {
    copy = new val.constructor()
    for (const key in val) {
      copy[key] = clone(val[key], iteratee)
    }
  } else if (isArray(val)) {
    copy = new val.constructor(val.length)
    for (let i = 0, l = val.length; i < l; i++) {
      copy[i] = clone(val[i], iteratee)
    }
  } else {
    copy = val
  }
  return iteratee?.(copy) ?? copy
}

Any guide for installation and setup?

It may seem newbie but I couldn't make it work so far. I'm a little confused, I don't know what should I do with those packages. Even example package didn't make much sense to me.

Anyway, I think this cool project needs some kind of getting started guide

Decorators are incombatible with typescript and follow a legacy standard

As discussed on the phone, the decorators used in the controllers are following a legacy standard and are not compatible with typescript. The active version of the Decorators proposal only supports decorators on classes – not within object literals as Dito uses in the collection and member object literals.

One solution discussed was be to use a helper function to create the actions:

export class MyModels extends ModelController {
  modelClass = MyModel;

  collection = {
    allow: ['find', 'helloCollection'],
    index: createAction({
      action: ['get', '.'],
      returns: { type: 'string' },
      handler: () => 'Hello from the index action. Note: its method name does not matter.',
    }),
    helloCollection: createAction({
      parameters: [
        {
          name: 'msg',
          type: 'string',
          required: true,
        },
      ],
      handler: (msg) => `Model class '${this.modelClass.name}' says hello: ${msg}`,
    }),
  };
}

Typescript types

I was looking at how Typescript types might be approached in the library using the Application configuration object as a starting point. One nicety is that you can easily import the types from underlying libraries. See for example the app.cors configuration..

Some other things I noticed while doing the initial typing:

  • env could default to process.env.NODE_ENV when not supplied
  • server.host could default to env.NODE_HOST || env.HOST || '0.0.0.0' when not supplied
  • server.port could default to env.NODE_PORT || env.PORT || 8080 when not supplied
  • log and server could be made optional like app is
import type { Config } from 'knex';
import type { KnexSnakeCaseMappersFactory } from 'objection';
import type { Options as CorsOptions } from '@koa/cors';
import type { CompressOptions } from 'koa-compress';
import type koaSession from 'koa-session';

export interface Configuration {
  /**
   * @defaultValue `production`
   */
  env?: 'production' | 'development';
  /**
   * The server configuration
   */
  server: {
    /**
     * The ip address or hostname used to serve requests
     */
    host: string;
    /**
     * The port to listen on for connections
     */
    port: number;
  };
  /**
   * Logging options
   */
  log: {
    /**
     * Enable logging requests to console by passing `true` or pick between
     * 'console' for logging to console and 'file' for logging to file
     * @defaultValue `false`
     */
    requests?: boolean | 'console' | 'file';
    /**
     * Whether to output route (Controller) logs
     * @defaultValue `false`
     */
    routes?: boolean;
    /**
     * Whether to log relation mappings
     * @defaultValue `false`
     */
    relations?: boolean;
    /**
     * Whether to log json schema
     * @defaultValue `false`
     */
    schema?: boolean;
    /**
     * Whether to log sql queries
     * @defaultValue `false`
     */
    sql?: boolean;
  };
  app?: {
    /**
     * Whether to normalize paths from camel case to kebab case
     * @defaultValue `false`
     */
    normalizePaths?: boolean;
    /**
     * @defaultValue `false`
     */
    proxy?: boolean;
    /**
     * Whether to include X-Response-Time header in responses
     * @defaultValue `true`
     */
    responseTime?: boolean;
    /**
     * Whether to use koa-helmet middleware which provides important security
     * headers to make your app more secure by default.
     * @defaultValue `true`
     */
    helmet?: boolean;
    /**
     * Enable or configure Cross-Origin Resource Sharing (CORS)
     * @defaultValue `true`
     */
    cors?: boolean | CorsOptions;
    /**
     * Enable or configure server response compression
     * @defaultValue `true`
     */
    compress?: boolean | CompressOptions;
    /**
     * Enable ETag headers in server responses
     * @defaultValue `true`
     */
    etag?: boolean;
    /**
     * @defaultValue `false`
     */
    session?: boolean | ({ modelClass: 'string' } & koaSession.opts);
    /**
     * Enable passport authentication middleware
     * @defaultValue `false`
     */
    passport?: boolean;

    // csrf: boolean,            // TODO: Implement

    /**
     * Keys used for session
     */
    keys?: string[];
  };
  knex: {
    /**
     * The database client to use - see http://knexjs.org/#Installation-client
     */
    client: Config['client'];
    /**
     * The database connection configuration - see http://knexjs.org/#Installation-client
     */
    connection: Config['connection'];
    /**
     * @defaultValue `false`
     */
    normalizeDbNames?: boolean | Parameters<KnexSnakeCaseMappersFactory>;
    /**
     * Whether to replace undefined keys with NULL instead of DEFAULT
     * @defaultValue `false`
     */
    useNullAsDefault?: Config['useNullAsDefault'];
  };
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.