Skip to content

Design doc

Justin Meyer edited this page May 17, 2019 · 50 revisions

Big Example

The following example shows most of the possibilities:

import {DefineClass, type} from "can/ecosystem";

MyExampleType extends DefineClass {
  static define = {

  }

  get property(){}
  set property(){}

  method(){}
}

Packages, modules and exports

Currently, we are going to release the following packages with associated exports and named can/ecosystem exports:

  • can-define-class exports the DefineClass constructor. import {DefineClass} from "can/ecosystem"
  • can-define-array exports the DefineArray constructor. import {DefineArray} from "can/ecosystem"
  • can-stache-define-element exports the StacheDefineElement constructor. import {StacheDefineElement} from "can/ecosystem"
  • can-type exports an object of properties and methods. import {type} from "can/ecosystem"

Issues:

  • Should define-class have a single export
  • DefineArray package name
  • Does DefineArray inherit from Array? If it does, there will be bugs due check to Array.isArray assuming non observable.
    • DefineArray overwrites array methods so events will fire. This is why an Array type is needed.
    • A proxy version of DefineArray is very useful / important. However, is this incompatible with class?
  • Is there a "version locking problem" with having can-define-class and can-define-list? cc @kevin

Reflect

The following are some important methods:

  • canReflect.convert()
  • canReflect.new()
  • canReflect.isConstructorLike()
  • canReflect.isMember() ... does this exist?

TypeObject

Properties:

  • can.new(value) - Throw if not allowed, otherwise converts to the type.
  • can.isMember(value) - Return true if a member of Type

ConstructorFunction

Any constructor function, including Builtins that pass canReflect.isConstructorLike()

Examples:

Number
class Todo {
 method(){}
}
Date

can-type

This is an improved can-data-types. 3.0 had a can-types.

Examples:

age: types.maybe(Number), //-> {[can.new](), schema: {type:"or", values: [Number, null]}}
age: types.convert(Number), //-> {[can.new]()}
    ALTERNATES:
    types.coerce(Number)

age: types.maybeConvert(Number),
    ALTERNATES:
    age: types.loose(Number)

age: types.check(Number)
    ALTERNATES:
    age: types.strict(Number)


age: types.any 
    - not "*"

Issues:

type.maybe( type: ConstructorFunction | TypeObject ): TypeObject

This converts a type Type to allow null and undefined.

Params:

  • type - A constructor function or TypeObject

Returns:

  • A TypeObject

type.convert( type: ConstructorFunction | TypeObject ): TypeObject

type.check( type: ConstructorFunction | TypeObject): ConstructorFunction | TypeObject

This will return a type that will throw if can.new is called with value that is not already the right type.

canReflect.new( type.check(String), 1 ) //-> throws

type.any: TypeObject

A type that allows anything to be set.

canReflect.new( type.any, 1 ) //-> 1
canReflect.new( type.any, {} ) //-> {}

DefineClass

PropDefinitions

Schema:

class Extended extends DefineClass {
  static define = PropDefinitions {
    [key: String]:
       Primitive |
       PrimitiveFunction | 
       ConstructorFunction |
       TypeObject |
       PropDefinition |
  }
}

Example:

class Extended extends DefineClass {
  static define = {
    age: 36,                    // Primitive
    date: Number                // PrimitiveFunction
    date: Date                  // Constructor function
    name: type.convert(String)  // TypeObject
    completed: {default: false} // PropDefinition
  }
}

Depending on the key's value in a prop definition, the following will happen:

  • Primitive - Uses the primitive as the default value. This also sets the type to match the type of the primitive value.

    Issues:

  • PrimitiveFunction - Sets the type to the ConstructorFunction value. This will be have "strict" typing. Primitives like Number will use a typeof check.

  • ConstructorFunction - Sets the type to the ConstructorFunction value. This will be have "strict" typing. Non primitives will use an instanceof check.

  • TypeObject- This will be the type, set as-is.

  • PropDefinition - If an object is passed, it will be treated as a PropDefinition object.

Issues:

  • What if the PropDefinition object is empty? This was a shorthand for the any type in can-define.
  • What if a function is specified? Could this be treated as a default value too? This would be nice for views:
    static define = {
      renderer: stache("{{foo}}")
    }

PropDefinition

Schema:

{
  type: 
        TypeObject | 
        PrimitiveFunction |
        ConstructorFunction | 
        FunctionFunction      //?

  default: 
        Primitive |
        Function |
        Object

  get default(){}
  
  get(){}
  set( [newVal] [,lastSet] ){}
  async( [resolve] ){}
  value( {resolve, listenTo, stopListening, lastSet}){}
}

Example:

{
  type: type.convert(String) // TypeObject
  type: Date                 // ConstructorFunction
  type: Number               // PrimitiveFunction
  type: Function             // FunctionFunction ??

  default: 1                 // Primitive
  default(){ return {}; }    // Function
  default: {}                // Object

  get default(){ return {}; }
  
  get(){
    return this.first + " " + this.last;
  }
  set(){
    this.sideEffect = 1;
  }
  set(value){ 
    return value;
  }
  set(value, currentValue){
    return currentValue;
  } 
  async(){
    return Todo.getList();
  }
  async(resolve){
    Todo.getList().then(resolve);
  }
  value( {resolve, listenTo, stopListening, lastSet}){
    resolve(5);
  }
}

type

default

Does the following based on the value:

  • Primitive - Uses that primitive value as a default value.
  • Function - Calls that function so that this is the instance. The return value of that function will be used as the initial value of the property.
  • Object - Warn user to use get default(){}.

Issues:

  • Allow providing a primitive as the default value
  • Support a resolve
  • It is hard to have default functions. If type: Function do we take a function if provided?
  • As an alternative to warning when an Object is passed, we could either clone it or Object.create() it.

get default

Issues:

get

set

Issues:

async

value

Props

class ExtendedDefineClass from DefineClass {

  • static seal = true; - defaults to true https://github.com/canjs/can-define-class/issues/35

  • static define = {

    • "any" - https://github.com/canjs/can-define-class/issues/36

      • default for properties
      • conflicts?
      • what about # for arrays
      • ¿ somehow default to maybe by default?
    • prop: PropDefinition {

      • type: PrimitiveFunction

        • don't call new, instead use typeof
      • type: Function

        • use instance of
      • type: {[can.new], [can.isMember]},

        • strictness and coercion comes with the type
      • default: primitive https://github.com/canjs/can-define-class/issues/37 https://github.com/canjs/can-define-class/issues/38

      • default: object

        • can-define warns that the object is shared

        • ¿ Object.create it ?

        • ¿ clone it ?

        • default: function.call(this, /resolve/)

          • calls and returns the value ... hard to have default functions
          • common place is views
          • ¿ if type is Function, do we just take a function ?
        • defaultValue

        • get default(){ return {} }, https://github.com/canjs/can-define-class/issues/39

        • default: {},

        • get(){},

        • set([newVal] [,lastSet, ¿resolve?]) { . https://github.com/canjs/can-define-class/issues/40 this.prop //-> side effect return returnValue //-> 0 arguments -> // undefined -> leaves current value // otherwise -> sets the value
          ¿ why not value ... property needs to be bound to know something was set ? },

             async( resolve ){
                 // COMPUTED
             },
             value( {listenTo, resolve} ) https://github.com/canjs/can-define-class/issues/41
                 ¿ resolve and resolve  ?
          
             required: Boolean=false,
             enumerable: Boolean,
             serialize: function(value){}
          

          }

        prop: Function ¿ prevent returning default value ? prop: {[can.new]}
        prop: primitiveValue (ex: prop: 0) ¿ worth it ? ¿ add string(TypeOf(primitiveValue)) ? } get prop(){ } set prop(){ }

      method(){}

      // inherited listenTo() ¿ bind click on elemeent ?

      get(){}, ¿ are we going to have the proxy soon then might not be needed? https://github.com/canjs/can-define-class/issues/42 - if sealed is true ... you shouldn't even have a proxy. set(){}

      static can.defineInstanceProperty //-> can-connect can add _saving }

DefineClass instances

new DefineClass

var defineClass = new DefineClass({})

new ExtendedDefineClass

new ExtendedDefineClass({propNotDefined: "value"}) https://github.com/canjs/can-define-class/issues/44

  • can-define does not complain ...
  • but can-define-class should complain ... set seal to false if you want this.

How to define "hungry" properties

https://github.com/canjs/can-define-class/issues/45

HungryDefineArrayOrObject {

}

HugryList  extends DefineArray {
    define {
        "#": {
            type: type.maybeConvert([Hungry|DefineList]) // does not work
        }
    }
}

Hungry extends DefineClass {
    define {
        "*": {
            type: type.maybeConvert([Hungry|DefineList]) // does not work
        }
    }
}

Todo extends DefineClass {
    static define = {
        meta: Hungry
    }
}


var todo = new Todo();

todo.meta = {
    foo: {bar: {}}
}

todo.meta.foo.bar.zed = "ted"

Setting properties related to Type

class Type extends DefineClass {
  static define = { prop: Number }
}
const instance = new Type();
instance.prop = "1";  //-> throws `"1" is not a Number`
instance.prop = null //-> throws `null is not a Number`
Clone this wiki locally