-
Notifications
You must be signed in to change notification settings - Fork 1
Design doc
The following example shows most of the possibilities:
import {DefineClass, type} from "can/ecosystem";
MyExampleType extends DefineClass {
static define = {
}
get property(){}
set property(){}
method(){}
}
Currently, we are going to release the following packages with associated exports and named can/ecosystem
exports:
-
can-define-class
exports theDefineClass
constructor.import {DefineClass} from "can/ecosystem"
-
can-define-array
exports theDefineArray
constructor.import {DefineArray} from "can/ecosystem"
-
can-stache-define-element
exports theStacheDefineElement
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
The following are some important methods:
canReflect.convert()
canReflect.new()
canReflect.isConstructorLike()
-
canReflect.isMember()
... does this exist?
-
can.new(value)
- Throw if not allowed, otherwise converts to the type. -
can.isMember(value)
- Return true if a member of Type
Any constructor function, including Builtins that pass canReflect.isConstructorLike()
Examples:
Number
class Todo {
method(){}
}
Date
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:
- [Should this be kept in can-data-types]
- Naming of the type functions
This converts a type Type
to allow null and undefined.
Params:
-
type
- A constructor function orTypeObject
Returns:
- A
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
A type that allows anything to be set.
canReflect.new( type.any, 1 ) //-> 1
canReflect.new( type.any, {} ) //-> {}
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 thetype
to match the type of the primitive value.Issues:
-
PrimitiveFunction
- Sets the type to theConstructorFunction
value. This will be have "strict" typing. Primitives likeNumber
will use atypeof
check. -
ConstructorFunction
- Sets the type to theConstructorFunction
value. This will be have "strict" typing. Non primitives will use aninstanceof
check. -
TypeObject
- This will be the type, set as-is. -
PropDefinition
- If an object is passed, it will be treated as aPropDefinition
object.
Issues:
- What if the
PropDefinition
object is empty? This was a shorthand for theany
type incan-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}}") }
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);
}
}
Does the following based on the value:
-
Primitive
- Uses that primitive value as a default value. -
Function
- Calls that function so thatthis
is the instance. The return value of that function will be used as the initial value of the property. -
Object
- Warn user to useget 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 orObject.create()
it.
Issues:
Issues:
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 usetypeof
- don't call
-
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 }
-
-
var defineClass = new DefineClass({})
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.
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"
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`