Giter Club home page Giter Club logo

wool's People

Contributors

lsjroberts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

wool's Issues

Creating Web Apps

Creating Web Apps

Following on from #2, the wool/browser package provide programs for creating websites and applications.

It uses the same underlying wool/ui package as wool/terminal and wool/native to describe the layout of its elements.

Programs

All these programs allow the website / app to be built into the html, css and javascript artifacts.

wool run hello/world build

wool/browser is a disappearing framework in a similar vein to Svelte. When compiled, the framework code that is required is embedded into your app and the rest is ditched.

It is a functional library, with a single store of state at the root and declarative view functions that describe how that state is presented.

Sandbox

A sandbox application can not communicate with the outside world but can react to user input.

interface Sandbox<Model, Msg> {
  init: () => Model;
  view: (model: Model) => UI.Layout;
  update: (msg: Msg, model: Model) => Model;
}
import * as Browser from 'wool/browser';
import { layout, el, text } from 'wool/ui';

export default Browser.sandbox({
  init: () => {},
  view: (model) => layout([], el([], text`Hello, World!`)),
  update: (msg, model) => model,
});

The syntax and functionality of wool/ui is a separate work-in-progress.

Messages

Events are declaratively added to interactions, such as pressing a button. The event is given a message that will be passed into the program's update function. This can then be used to update the model. The new model is then passed into the view function to create the new view.

Custom types are discussed in #3

import * as Browser from 'wool/browser';
import { Maybe, Type } from 'wool/core';
import { Events, Input, layout, column, el, button, text } from 'wool/ui';

interface Model {
  greeting: string;
}

const SetGreeting = Type.custom('SetGreeting', Type.string);

const init = (): Model => ({
  greeting: 'Hello',
});

const view = ({ greeting }: Model) => layout(
  [],
  column([], [
    text`${greeting}, World!`,
    Input.button([], {
      onPress: Maybe.just(SetGreeting('Bonjour')),
      label: el([], text`Change greeting`),
    }),
  ]),
);

const update = (msg, model) => Type.matchOn(msg, {
  [SetGreeting]: (greeting) => ({ greeting }),
});

export default Browser.sandbox({ init, view, update });

Application

An application provides access to the document head and url. Though a routed application is probably more useful.

interface Application<Model, Msg> {
  init: (url: Url, key?) => Tuple<Model, Cmd<Msg>>;
  view: (model: Model) => Document<Msg>;
  update: (msg: Msg, model: Model) => Tuple<Model, Cmd<Msg>>;
  onUrlRequest: (req: UrlRequest) => Msg;
  onUrlChange: (url: Url) => Msg;
}

interface Document<Msg> {
  head: { title: String };
  body: UI.Layout;
}

Routed Application

The routed application is a simplified application that handles routing for you. This is probably the ideal for most web apps.

interface RoutedApplication<Model, Msg> {
  init: (url: Url, key?) => Tuple<Model, Cmd<Msg>>;
  view: (model: Model) => Document<Msg>;
  update: (msg: Msg, model: Model) => Tuple<Model, Cmd<Msg>>;
}

It will populate your model with the updated route whenever it changes.

const init = () => {};
const update = (msg, model) => model;
const view = (model) => ({
  head: [],
  body: layout(
    [],
    Type.matchOn(model[Browser.routeKey], {
      '/': () => el([], text`Welcome`),
      '/with-params': ({ greeting }) => el([], text`${greeting}`),
    }),
  ),
});

export default Browser.routedApplication({ init, update, view });
Possible implementation of Browser.routedApplication
const RouteRequest = Type.custom('RouteRequest', Browser.Type.routeRequest);
const RouteChange = Type.custom('RouteChange', Browser.Type.routeChange);

const routeKey = Type.custom('routeKey', Type.string);

Browser.routedApplication = (config) => Browser.application({
  init: () => ({
    ...config.init(),
    [routeKey]: new Route('/'),
  }),
  view: config.view,
  update: (msg, model) => {
    case newModel = Type.matchOn(msg, {
      [RouteRequest]: (req) => ({...model, route: req }),
      [RouteChange]: (url) => model,
    });

    return {
      ...config.update(msg, newModel),
      [routeKey]: newModel[routeKey],
    };
  },
  onUrlRequest: (req) => RouteRequest(req),
  onUrlChange: (url) => RouteChange(url),
});

Questions

Extra programs?

This design borrows heavily from elm/browser, which provides two further programs: element and document.

The element program provides a way to embed an elm app within an existing webpage, which won't be needed with wool.

The document program is the same as the application but without the url routing. Is that useful? Or can users who want that just noop the url functions on application?

Creating CLI Apps

At the root of Wool's philosophy are three concepts:

  1. A single, good way to achieve a task
  2. A small number of high quality packages
  3. Sandboxed and secure by default (see #1)

To that end, the wool/terminal package will provide a way to create command line applications from simple single commands to git-style programs with sub-commands and curses / blessed style views.

All applications, be they cli or web-based, will be expected to export a default entry. This is much the same as a main function in many common languages such as C and Python.

Programs

All these programs would automatically support --help and --version, e.g.:

wool run hello/world --version
> 1.0.0

And as per #1 they would require the user to give them permission before being able to perform dangerous actions such as calling functions from wool/fs and similar.

Action

A single action that takes no input from the outside world.

// ~/hello-world/wool.json
{
  "name": "hello/world",
  "entry": "index.ts"
}
// ~/hello-world/index.ts
import * as Terminal from 'wool/terminal';

export default Terminal.action(() => {
  console.log('Hello, World!');
});
wool make ~/hello-world
wool run hello/world
> Hello, World!

Command

A command that can be given arguments and flags.

import { command, arg, flag, required, alias, boolean, string } from 'wool/terminal';

export default Terminal.command({
  args: [
    arg('entry', required()),
  ],
  flags: [
    flag('clean', alias('c'), boolean(false)),
    flag('outDir', alias('o'), string('./dist')),
  ],
  action: (cmd) => {
    if (cmd.clean) {
      // ... clean the `cmd.outDir` ...
    }

    // ... do something with `cmd.entry` ...
  },
});
wool run hello/world ./src/index.ts --outDir ./out

Application

An application that can call off to many commands.

import { application, command } from 'wool/terminal';

export default application({
  commands: {
    one: command({ ... }),
    two: command({ ... }),
  }
})
wool run hello/world one ./src/index.ts --outDir ./out

UI Program

A curses / blessed style view.

import * as Terminal from 'wool/terminal';
import { layout, el, text } from 'wool/ui';

export default Terminal.ui({
  args: [...],
  flags: [...],
  init: (cmd) => {},
  update: (msg, model) => {},
  view: (model) => layout([], el([], text`Hello, World!`)),
})
wool run hello/world

The syntax and functionality of wool/ui is a separate work-in-progress.

Questions

Command argument and flag syntax

Above, I've used a variable args syntax:

flag('clean');
flag('clean', alias('c'), boolean(false))

A strictly FP approach would look like:

boolean(alias(flag('clean'), 'c'), false)

But this is clearly a bit rubbish.

Another alternative would be chained functions, e.g.:

flag('clean').alias('c').boolean(false)

Package Registries

[TODO: placeholder, to be expanded)

  1. Can a registry be a static file server?
    a. Ownership transference key generated as a combination of package and an entered password
    b. Publishing through ssh keys associated with the files on the server?
    c. How would they upload? Don't want to allow file access to randoms.

  2. If not static, a docker container.
    a. What would be the upgrade path for a deployed registry? Without wiping out existing published packages.

  3. Auth method?
    a. Portier
    b. Certificates

Security & Package Authorship

In light of the recent event-stream security issue, and the previous left-pad debacle, it seems sensible to open discussion on approaches to more secure methods of guaranteeing authorship and safe publishing & installation of packages.

What exists already?

note: "exists" refers to in the design, not code, wool is still very early pre-alpha

Indirect packages

In its current design, wool helps surface indirect / transient packages. This helps developers be fully aware of the quantity of dependencies in their project and whose code is running.

wool add hello/world
To add hello/world I would like to install the following packages:

    hello/world  1.2.0  1.0.0 <= v < 2.0.0
    hello/other  2.0.0  2.0.0 <= v < 3.0.0

Should I install these packages? [Y/n]
{
  "dependencies": {
    "direct": {
      "hello/world": "1.0.0 <= v < 2.0.0"
    },
    "indirect": {
      "hello/other": "2.0.0 <= v < 3.0.0"
    }
  }
}

Automatic semver

It also only allows a single entry point into packages with an automatically semantically versioned interface.

Registries

When creating a project, the developer must choose which package registries to point at (defaulting to just registry.wool.org).

{
  "registries": ["registry.wool.org"]
}

All registries must be accessible over https.

When adding a package, its registry is stored in the wool.lock, e.g.

{
  "hello/world": {
    "version": "1.2.0",
    "constraint": "1.0.0 <= v < 2.0.0",
    "registry": "registry.wool.org"
  }
}

Further ideas

Package permissions and sandboxing

Restricted default file access

Most packages do not need direct access to the filesystem, so by default they should not be allowed to read or write anywhere.

Restricted default network access

Most packages also do not need network access, so by default they should also not be allowed to send network requests.

Granting permissions

Packages should be provided opt-in permissions to let them perform certain, risky actions.

When running wool make ., wool would warn them that a package does not have the permissions it requires:

❖ PERMISSIONS REQUIRED --------------------------------------------- hello/world

The package hello/world requires the following permissions:

    "read": true
    "write: ["./path/to/folder"]

Do you wish to grant these permissions to hello/world? [y/N]

This would then update their wool.json and re-attempt the compilation.

{
  "dependencies": {
    "direct": {
-     "hello/world": "1.0.0 <= v < 2.0.0"
+     "hello/world": {
+       "constraint": "1.0.0 <= v < 2.0.0",
+       "permissions": {
+         "read": true,
+         "write": ["./path/to/folder"]
+       }
+     }
    }
  }
}

For example, to restrict file access this can be acheived through the wool loader by preventing installed packages from using the fs package directly.

Instead import via wool/fs (with the loader explicitly only allowing the wool namespace direct access to node in-built packages).

-import * as fs from 'fs';
+import * as fs from 'wool/fs';

Publish verification

2-factor authentication should be mandatory for all publishing, even to private registries.

Package archival

It should be possible for maintainers to archive packages and put them in a read-only mode.

wool archive .

Both archiving and unarchiving a package would require 2-factor authentication.

Developers adding archived projects will be warned:

❖ PACKAGE ARCHIVED ----------------------------------------------- hello/world

The package hello/world has been archived and is no longer being maintained.
It may present a security risk for your project if there are un-patched
vulnerabilities.

Do you wish to continue using hello/world? [y/N]
{
  "dependencies": {
    "direct": {
-     "hello/world": "1.0.0 <= v < 2.0.0"
+     "hello/world": {
+       "constraint": "1.0.0 <= v < 2.0.0",
+       "allowArchived": true
+     }
    }
  }
}

Transferal of ownership

It should be feasible to transfer ownership of a project that the maintainer no longer wishes to support.

This would involve the owner pushing a "transference key" to the registry, this would immediately lock the package and prevent any new publications. They would then share the key with the intended new owner, who would then use it to unlock and take control of the package.

Only one account should be able to publish a package at any given time.

Transferal of ownership would enforce the next publication to be a major version update bump.

If a developer updates their constraint to the next major version they will be warned about the ownership change.

❖ PACKAGE OWNER CHANGED --------------------------------------- hello/world

The package `hello/world` has transferred ownership from:

    [email protected]

to:

    [email protected]

Before updating from 1.2.0 to 2.0.0 you should check the new package owner
is trustworthy.

Do you wish to continue? [y/N]

Checksum imports

When compiling, check the imported package code against their checksums stored in the wool.lock.

{
  "hello/world": {
    "version": "1.2.0",
    "constraint": "1.0.0 <= v < 2.0.0",
    "registry": "registry.wool.org",
    "checksum": "123456"
  }
}

This could be generated by running a concatenated toString() on the exported functions & values. If another package mutates the function it will return a different toString() value and fail the checksum validation.

e.g.

import hello from 'hello/world'

let originalGreet = hello.greet;
hello.greet = async (message) => {
  await mineSomeBitcoin();
  return hello.greet(message);
};

Questions

Decentralisation and package name clashes

One of the main issues around a decentralised approach to package registries is namespace squatting and package name clashes.

Even from non-malicious actors this would be confusing and annoying.

If a package name exists on multiple registries, the user will be asked to confirm which registry they wish to install from:

❖ PACKAGE NAME CLASH --------------------------------------- hello/world

The package `hello/world` exists on more than one registry:

    registry.wool.org
    registry.wool-community.net

Which registry should I download this package from?    # multiple choice input
{
  "registries": ["registry.wool.org"]
  "dependencies": {
    "direct": {
-     "hello/world": "1.0.0 <= v < 2.0.0"
+     "hello/world": {
+       "constraint": "1.0.0 <= v < 2.0.0",
+       "registry": "registry.wool.org"
+     }
    }
  }
}

Is this enough? Is there any sensible way to prevent name clashing between decentralised registries?

What about namespace squatting?

Say the company hello registers their namespace on registry.wool.org, and publishes hello/world.

Then on another registry, for example registry.wool-community.net, an unaffiliated user registers hello. What recourse can the company hello take? Can the user mimic the company and freely cause confusion?

Should there be a single central authority for public namespace registration? Ideally not.

Perhaps allow whole namespaces to be tied to registries, instead of on a per-package basis.

{
  "registries": [
    "registry.wool.org",
+   {
+     "namespace": "hello",
+     "registry": "registry.hello.com"
+   }
  ],
  ...
}

And importantly, how can wool assuage non-developer stakeholders of the risks?

Commit verification

Should publishing be tied to the git repository? The primary downside is it would force package authors to use git, however this is by far the most dominant vcs used. And security is more important than convenience.

This would allow commit verification as a requirement.

It could be an opt-in for registries, i.e. a registry could chose to force all users to verify their commits.

Anything else?

There are probably numerous other issues and attack vectors possible.

Custom Types

Custom types are also known as "union types", "tagged unions" and "algebraic data types".

While typescript provides a way to have compile-time union types, they are no longer available at runtime, which means they can't be used for active behaviours (such as messages in wool/browser).

Basic usage

import { Type } from 'wool/core';

const Increment = Type.custom('Increment');
const Decrement = Type.custom('Decrement');

let value = 0;

const msg = Increment();

Type.matchOn(msg, {
  [Increment]() { value = value + 1; },
  [Decrement]() { value = value - 1; },
});

console.log(value);
// 1

Parameters

import { Type } from 'wool/core';

const SetValue = Type.custom('SetValue', Type.int);

const msg = SetValue(42);

Type.matchOn(msg, {
  // ...
  [SetValue](newValue) { value = newValue; },
});

console.log(value);
// 42
import { String, Type } from 'wool/core';

const SetGreeting = Type.custom('SetGreeting', Type.string, Type.int)

let value = 'Hi';

const msg = SetGreeting('Hello', 3);

Type.matchOn(msg, {
  [SetGreeting](greeting, times) {
    value = String.repeat(greeting, times);
  },
});

console.log(value);
// "HelloHelloHello"

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.