objectmodel 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.eslintignore +8 -8
  2. package/.eslintrc.json +25 -25
  3. package/.travis.yml +2 -2
  4. package/LICENSE +22 -22
  5. package/README.md +67 -67
  6. package/build/{add-banner.cjs → add-banner.js} +13 -13
  7. package/build/bundle-entry.dev.js +2 -2
  8. package/build/bundle-entry.js +11 -11
  9. package/build/{update-docs.cjs → update-docs.js} +7 -5
  10. package/dist/object-model.cjs +466 -472
  11. package/dist/object-model.js +466 -472
  12. package/dist/object-model.js.map +1 -1
  13. package/dist/object-model.min.js +2 -2
  14. package/dist/object-model.min.js.map +1 -1
  15. package/index.html +1603 -1603
  16. package/package.json +20 -10
  17. package/rollup.config.js +13 -13
  18. package/src/array-model.d.ts +16 -0
  19. package/src/array-model.js +68 -68
  20. package/src/devtool-formatter.js +198 -198
  21. package/src/function-model.d.ts +24 -0
  22. package/src/function-model.js +58 -65
  23. package/src/helpers.js +43 -43
  24. package/src/index.js +4 -4
  25. package/src/list-model.js +43 -43
  26. package/src/map-model.d.ts +18 -0
  27. package/src/map-model.js +48 -48
  28. package/src/object-model.d.ts +74 -0
  29. package/src/object-model.js +4 -3
  30. package/src/set-model.d.ts +16 -0
  31. package/src/set-model.js +41 -41
  32. package/test/array-model.spec.cjs +291 -291
  33. package/test/array-model.test-d.ts +24 -0
  34. package/test/basic-model.spec.cjs +263 -263
  35. package/test/basic-model.test-d.ts +30 -0
  36. package/test/bench/array.html +51 -51
  37. package/test/bench/bench-lib.js +49 -49
  38. package/test/bench/map-no-cast.html +53 -53
  39. package/test/bench/map-set.html +52 -52
  40. package/test/bench/map.html +51 -51
  41. package/test/bench/object-models.html +87 -87
  42. package/test/function-model.spec.cjs +161 -162
  43. package/test/function-model.test-d.ts +18 -0
  44. package/test/index.cjs +13 -13
  45. package/test/index.html +27 -27
  46. package/test/map-model.spec.cjs +224 -224
  47. package/test/map-model.test-d.ts +21 -0
  48. package/test/model.spec.cjs +30 -30
  49. package/test/object-model.spec.cjs +1345 -1327
  50. package/test/object-model.test-d.ts +53 -0
  51. package/test/set-model.spec.cjs +213 -213
  52. package/test/set-model.test-d.ts +17 -0
  53. package/test/umd.html +25 -25
  54. package/types/definitions.d.ts +43 -0
  55. package/types/helpers.d.ts +4 -0
  56. package/types/index.d.ts +6 -128
  57. package/test/lib/qunit.css +0 -436
  58. package/test/lib/qunit.js +0 -6582
  59. package/tsconfig.json +0 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objectmodel",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Strong Dynamically Typed Object Modeling for JavaScript",
5
5
  "author": "Sylvain Pollet-Villard",
6
6
  "license": "MIT",
@@ -18,21 +18,24 @@
18
18
  "watch:js": "npm run build:js -- --watch",
19
19
  "watch:min": "npm run build:min -- --watch",
20
20
  "info": "npm-run-all info:*",
21
- "info:banner": "node build/add-banner.cjs",
22
- "info:docs": "node build/update-docs.cjs",
21
+ "info:banner": "node build/add-banner.js",
22
+ "info:docs": "node build/update-docs.js",
23
23
  "lint": "eslint . --fix",
24
- "test": "qunit test/index.cjs"
24
+ "test": "npm-run-all test:*",
25
+ "test:unit": "qunit test/index.cjs",
26
+ "test:tsd": "tsd"
25
27
  },
26
28
  "devDependencies": {
27
29
  "cjyes": "^0.3.1",
28
- "eslint": "^7.28.0",
30
+ "eslint": "^8.18.0",
29
31
  "esm": "^3.2.25",
30
- "filesize": "^6.3.0",
31
- "gzip-size": "^6.0.0",
32
+ "gzip-size": "^7.0.0",
32
33
  "npm-run-all": "^4.1.5",
33
- "qunit": "^2.16.0",
34
- "rollup": "^2.51.2",
35
- "rollup-plugin-terser": "^7.0.2"
34
+ "pretty-bytes": "^6.0.0",
35
+ "qunit": "^2.19.1",
36
+ "rollup": "^2.75.6",
37
+ "rollup-plugin-terser": "^7.0.2",
38
+ "tsd": "^0.24.1"
36
39
  },
37
40
  "type": "module",
38
41
  "main": "dist/object-model.js",
@@ -41,6 +44,13 @@
41
44
  "require": "./dist/object-model.cjs"
42
45
  },
43
46
  "types": "types/index.d.ts",
47
+ "tsd": {
48
+ "directory": "test"
49
+ },
50
+ "engines": {
51
+ "npm": ">=7.0.0",
52
+ "node": ">=17.5.0"
53
+ },
44
54
  "keywords": [
45
55
  "typed",
46
56
  "types",
package/rollup.config.js CHANGED
@@ -1,14 +1,14 @@
1
- import { terser } from "rollup-plugin-terser";
2
-
3
- const isProduction = process.env.BUILD === 'production';
4
-
5
- export default {
6
- input: 'build/bundle-entry' + (isProduction ? '.js' : '.dev.js'),
7
- output: {
8
- file: 'dist/object-model' + (isProduction ? '.min.js' : '.js'),
9
- format: 'esm',
10
- sourcemap: true,
11
- extend: true
12
- },
13
- plugins: isProduction ? [terser()] : []
1
+ import { terser } from "rollup-plugin-terser";
2
+
3
+ const isProduction = process.env.BUILD === 'production';
4
+
5
+ export default {
6
+ input: 'build/bundle-entry' + (isProduction ? '.js' : '.dev.js'),
7
+ output: {
8
+ file: 'dist/object-model' + (isProduction ? '.min.js' : '.js'),
9
+ format: 'esm',
10
+ sourcemap: true,
11
+ extend: true
12
+ },
13
+ plugins: isProduction ? [terser()] : []
14
14
  };
@@ -0,0 +1,16 @@
1
+ import { ModelDefinition, FromDefinition } from "../types/definitions";
2
+ import { Model } from "./object-model";
3
+
4
+ export interface ArrayModel<D> extends Model<D> {
5
+ (array: FromDefinition<D>[]): FromDefinition<D>[];
6
+ new(array: FromDefinition<D>[]): FromDefinition<D>[];
7
+
8
+ extend<E extends ModelDefinition[]>(...extensions: E): ArrayModel<E extends [] ? D : [D, ...E]>
9
+ }
10
+
11
+ export interface ArrayModelConstructor {
12
+ <D extends ModelDefinition>(itemDefinition: D): ArrayModel<D>;
13
+ new<D extends ModelDefinition>(itemDefinition: D): ArrayModel<D>;
14
+ }
15
+
16
+ export const ArrayModel: ArrayModelConstructor;
@@ -1,69 +1,69 @@
1
- import {
2
- _original, _check, cast, checkAssertions, checkDefinition,
3
- extendDefinition, extendModel, formatDefinition, Model, stackError, unstackErrors
4
- } from "./object-model.js"
5
- import { initListModel } from "./list-model.js"
6
- import { extend } from "./helpers.js"
7
-
8
- export default function ArrayModel(initialDefinition) {
9
- const model = initListModel(
10
- Array,
11
- ArrayModel,
12
- initialDefinition,
13
- a => Array.isArray(a) ? a.map(arg => cast(arg, model.definition)) : a,
14
- a => [...a],
15
- {
16
- "copyWithin": [],
17
- "fill": [0, 0],
18
- "pop": [],
19
- "push": [0],
20
- "reverse": [],
21
- "shift": [],
22
- "sort": [],
23
- "splice": [2],
24
- "unshift": [0]
25
- },
26
- {
27
- set(arr, key, val) {
28
- return controlMutation(model, arr, key, val, (a, v) => a[key] = v, true)
29
- },
30
-
31
- deleteProperty(arr, key) {
32
- return controlMutation(model, arr, key, undefined, a => delete a[key])
33
- }
34
- }
35
- )
36
-
37
- return model
38
- }
39
-
40
- extend(ArrayModel, Model, {
41
- toString(stack) {
42
- return "Array of " + formatDefinition(this.definition, stack)
43
- },
44
-
45
- [_check](arr, path, errors, stack) {
46
- if (Array.isArray(arr))
47
- (arr[_original] || arr).forEach((a, i) => checkDefinition(a, this.definition, `${path || "Array"}[${i}]`, errors, stack))
48
- else stackError(errors, this, arr, path)
49
-
50
- checkAssertions(arr, this, path, errors)
51
- },
52
-
53
- extend(...newParts) {
54
- return extendModel(new ArrayModel(extendDefinition(this.definition, newParts)), this)
55
- }
56
- })
57
-
58
- const controlMutation = (model, array, key, value, applyMutation, canBeExtended) => {
59
- const path = `Array[${key}]`
60
- const isInDef = (+key >= 0 && (canBeExtended || key in array))
61
- if (isInDef) value = checkDefinition(value, model.definition, path, model.errors, [], true)
62
-
63
- const testArray = [...array]
64
- applyMutation(testArray)
65
- checkAssertions(testArray, model, path)
66
- const isSuccess = !unstackErrors(model)
67
- if (isSuccess) applyMutation(array, value)
68
- return isSuccess
1
+ import {
2
+ _original, _check, cast, checkAssertions, checkDefinition,
3
+ extendDefinition, extendModel, formatDefinition, Model, stackError, unstackErrors
4
+ } from "./object-model.js"
5
+ import { initListModel } from "./list-model.js"
6
+ import { extend } from "./helpers.js"
7
+
8
+ export default function ArrayModel(initialDefinition) {
9
+ const model = initListModel(
10
+ Array,
11
+ ArrayModel,
12
+ initialDefinition,
13
+ a => Array.isArray(a) ? a.map(arg => cast(arg, model.definition)) : a,
14
+ a => [...a],
15
+ {
16
+ "copyWithin": [],
17
+ "fill": [0, 0],
18
+ "pop": [],
19
+ "push": [0],
20
+ "reverse": [],
21
+ "shift": [],
22
+ "sort": [],
23
+ "splice": [2],
24
+ "unshift": [0]
25
+ },
26
+ {
27
+ set(arr, key, val) {
28
+ return controlMutation(model, arr, key, val, (a, v) => a[key] = v, true)
29
+ },
30
+
31
+ deleteProperty(arr, key) {
32
+ return controlMutation(model, arr, key, undefined, a => delete a[key])
33
+ }
34
+ }
35
+ )
36
+
37
+ return model
38
+ }
39
+
40
+ extend(ArrayModel, Model, {
41
+ toString(stack) {
42
+ return "Array of " + formatDefinition(this.definition, stack)
43
+ },
44
+
45
+ [_check](arr, path, errors, stack) {
46
+ if (Array.isArray(arr))
47
+ (arr[_original] || arr).forEach((a, i) => checkDefinition(a, this.definition, `${path || "Array"}[${i}]`, errors, stack))
48
+ else stackError(errors, this, arr, path)
49
+
50
+ checkAssertions(arr, this, path, errors)
51
+ },
52
+
53
+ extend(...newParts) {
54
+ return extendModel(new ArrayModel(extendDefinition(this.definition, newParts)), this)
55
+ }
56
+ })
57
+
58
+ const controlMutation = (model, array, key, value, applyMutation, canBeExtended) => {
59
+ const path = `Array[${key}]`
60
+ const isInDef = (+key >= 0 && (canBeExtended || key in array))
61
+ if (isInDef) value = checkDefinition(value, model.definition, path, model.errors, [], true)
62
+
63
+ const testArray = [...array]
64
+ applyMutation(testArray)
65
+ checkAssertions(testArray, model, path)
66
+ const isSuccess = !unstackErrors(model)
67
+ if (isSuccess) applyMutation(array, value)
68
+ return isSuccess
69
69
  }
@@ -1,199 +1,199 @@
1
- import { _original, Model, Any, BasicModel, ObjectModel } from "./object-model.js"
2
- import ArrayModel from "./array-model.js"
3
- import SetModel from "./set-model.js"
4
- import MapModel from "./map-model.js"
5
- import FunctionModel from "./function-model.js"
6
- import { getProto, has, is, isFunction, isPlainObject } from "./helpers.js"
7
-
8
- const styles = {
9
- list: "list-style-type: none; padding: 0; margin: 0;",
10
- listItem: "padding: 0 0 0 1em;",
11
- model: "color: #3e999f;",
12
- instance: "color: #718c00; font-style: italic",
13
- function: "color: #4271AE",
14
- string: "color: #C41A16",
15
- number: "color: #1C00CF",
16
- boolean: "color: #AA0D91",
17
- property: "color: #8959a8",
18
- private: "color: #C19ED8",
19
- constant: "color: #8959a8; font-weight: bold",
20
- privateConstant: "color: #C19ED8; font-weight: bold",
21
- null: "color: #8e908c",
22
- undeclared: "color: #C0C0C0;",
23
- proto: "color: #B871BD; font-style: italic"
24
- };
25
-
26
- const getModel = (instance) => {
27
- if (instance === undefined || instance === null)
28
- return null
29
-
30
- const proto = getProto(instance);
31
- if (!proto || !proto.constructor || !is(Model, proto.constructor))
32
- return null
33
-
34
- return proto.constructor
35
- }
36
-
37
- const span = (style, ...children) => ["span", { style }, ...children]
38
-
39
- const format = (x, config = {}) => {
40
- if (x === null || x === undefined)
41
- return span(styles.null, "" + x);
42
-
43
- if (typeof x === "boolean")
44
- return span(styles.boolean, x);
45
-
46
- if (typeof x === "number")
47
- return span(styles.number, x);
48
-
49
- if (typeof x === "string")
50
- return span(styles.string, `"${x}"`);
51
-
52
- if (Array.isArray(x) && config.isModelDefinition) {
53
- return span("", ...x.flatMap(part => [format(part, config), " or "]).slice(0, -1))
54
- }
55
-
56
- if (isPlainObject(x))
57
- return formatObject(x, getModel(x), config)
58
-
59
- if (isFunction(x) && !is(Model, x) && config.isModelDefinition)
60
- return span(styles.function, x.name || x.toString());
61
-
62
- return ["object", { object: x, config }]
63
- }
64
-
65
- const formatObject = (o, model, config) => span("",
66
- "{",
67
- ["ol", { style: styles.list }, ...Object.keys(o).map(prop =>
68
- ["li", { style: styles.listItem }, span(styles.property, prop), ": ", format(o[prop], config)])
69
- ],
70
- "}"
71
- )
72
-
73
- const formatModel = model => {
74
- const
75
- cfg = { isModelDefinition: true },
76
- def = model.definition,
77
- formatList = (list, map) => list.flatMap(e => [map(e), ", "]).slice(0, -1);
78
- let parts = []
79
-
80
- if (is(BasicModel, model)) parts = [format(def, cfg)]
81
- if (is(ArrayModel, model)) parts = ["Array of ", format(def, cfg)]
82
- if (is(SetModel, model)) parts = ["Set of ", format(def, cfg)]
83
- if (is(MapModel, model)) parts = ["Map of ", format(def.key, cfg), " : ", format(def.value, cfg)]
84
- if (is(FunctionModel, model)) {
85
- parts = ["Function(", ...formatList(def.arguments, arg => format(arg, cfg)), ")"]
86
- if ("return" in def) parts.push(" => ", format(def.return, cfg))
87
- }
88
-
89
- if (model.assertions.length > 0) {
90
- parts.push("\n(assertions: ", ...formatList(model.assertions, f => ["object", { object: f }]), ")")
91
- }
92
-
93
- return span(styles.model, ...parts)
94
- }
95
-
96
- const ModelFormatter = {
97
- header(x, config = {}) {
98
- if (x === Any)
99
- return span(styles.model, "Any")
100
-
101
- if (is(Any.remaining, x))
102
- return span(styles.model, "...", format(x.definition, { isModelDefinition: true }))
103
-
104
- if (is(ObjectModel, x))
105
- return span(styles.model, x.name)
106
-
107
- if (is(Model, x)) {
108
- return formatModel(x)
109
- }
110
-
111
- if (config.isModelDefinition && isPlainObject(x))
112
- return format(x, config)
113
-
114
- return null;
115
- },
116
- hasBody(x) {
117
- return is(ObjectModel, x)
118
- },
119
- body(model) {
120
- return span("",
121
- "{",
122
- ["ol", { style: styles.list }, ...Object.keys(model.definition).map(prop => {
123
- const isPrivate = model.conventionForPrivate(prop)
124
- const isConstant = model.conventionForConstant(prop)
125
- const hasDefault = model.default && has(model.default, prop);
126
- let style = styles.property;
127
-
128
- if (isPrivate) {
129
- style = isConstant ? styles.privateConstant : styles.private
130
- } else if (isConstant) {
131
- style = styles.constant
132
- }
133
-
134
- return ["li", { style: styles.listItem },
135
- span(style, prop), ": ", format(model.definition[prop], { isModelDefinition: true }),
136
- hasDefault ? span(styles.proto, " = ", format(model.default[prop])) : ""
137
- ]
138
- })],
139
- "}"
140
- )
141
- }
142
- }
143
-
144
- const ModelInstanceFormatter = {
145
- header(x, config = {}) {
146
- if (config.isInstanceProperty && isPlainObject(x)) {
147
- return format(x, config)
148
- }
149
-
150
- const model = getModel(x);
151
- if (is(Model, model)) {
152
- const parts = is(ObjectModel, model) ? [model.name] : [["object", { object: x[_original] }], ` (${model.name})`];
153
- return span(styles.instance, ...parts)
154
- }
155
-
156
- return null;
157
- },
158
- hasBody(x) {
159
- return x && is(ObjectModel, getModel(x))
160
- },
161
- body(x) {
162
- const model = getModel(x)
163
- const o = x[_original] || x;
164
- return span("",
165
- "{",
166
- [
167
- "ol",
168
- { style: styles.list },
169
- ...Object.keys(o).map(prop => {
170
- const isPrivate = model.conventionForPrivate(prop)
171
- const isConstant = model.conventionForConstant(prop)
172
- const isDeclared = prop in model.definition;
173
- let style = styles.property;
174
-
175
- if (!isDeclared) {
176
- style = styles.undeclared
177
- } else if (isPrivate) {
178
- style = isConstant ? styles.privateConstant : styles.private
179
- } else if (isConstant) {
180
- style = styles.constant
181
- }
182
-
183
- return ["li", { style: styles.listItem },
184
- span(style, prop), ": ", format(o[prop], { isInstanceProperty: true })
185
- ]
186
- }),
187
- ["li", { style: styles.listItem },
188
- span(styles.proto, "__proto__", ": ", ["object", { object: getProto(x) }])
189
- ]
190
- ],
191
- "}"
192
- )
193
- }
194
- }
195
-
196
- if (typeof window !== "undefined") {
197
- window.devtoolsFormatters = (window.devtoolsFormatters || [])
198
- .concat(ModelFormatter, ModelInstanceFormatter);
1
+ import { _original, Model, Any, BasicModel, ObjectModel } from "./object-model.js"
2
+ import ArrayModel from "./array-model.js"
3
+ import SetModel from "./set-model.js"
4
+ import MapModel from "./map-model.js"
5
+ import FunctionModel from "./function-model.js"
6
+ import { getProto, has, is, isFunction, isPlainObject } from "./helpers.js"
7
+
8
+ const styles = {
9
+ list: "list-style-type: none; padding: 0; margin: 0;",
10
+ listItem: "padding: 0 0 0 1em;",
11
+ model: "color: #3e999f;",
12
+ instance: "color: #718c00; font-style: italic",
13
+ function: "color: #4271AE",
14
+ string: "color: #C41A16",
15
+ number: "color: #1C00CF",
16
+ boolean: "color: #AA0D91",
17
+ property: "color: #8959a8",
18
+ private: "color: #C19ED8",
19
+ constant: "color: #8959a8; font-weight: bold",
20
+ privateConstant: "color: #C19ED8; font-weight: bold",
21
+ null: "color: #8e908c",
22
+ undeclared: "color: #C0C0C0;",
23
+ proto: "color: #B871BD; font-style: italic"
24
+ };
25
+
26
+ const getModel = (instance) => {
27
+ if (instance === undefined || instance === null)
28
+ return null
29
+
30
+ const proto = getProto(instance);
31
+ if (!proto || !proto.constructor || !is(Model, proto.constructor))
32
+ return null
33
+
34
+ return proto.constructor
35
+ }
36
+
37
+ const span = (style, ...children) => ["span", { style }, ...children]
38
+
39
+ const format = (x, config = {}) => {
40
+ if (x === null || x === undefined)
41
+ return span(styles.null, "" + x);
42
+
43
+ if (typeof x === "boolean")
44
+ return span(styles.boolean, x);
45
+
46
+ if (typeof x === "number")
47
+ return span(styles.number, x);
48
+
49
+ if (typeof x === "string")
50
+ return span(styles.string, `"${x}"`);
51
+
52
+ if (Array.isArray(x) && config.isModelDefinition) {
53
+ return span("", ...x.flatMap(part => [format(part, config), " or "]).slice(0, -1))
54
+ }
55
+
56
+ if (isPlainObject(x))
57
+ return formatObject(x, getModel(x), config)
58
+
59
+ if (isFunction(x) && !is(Model, x) && config.isModelDefinition)
60
+ return span(styles.function, x.name || x.toString());
61
+
62
+ return ["object", { object: x, config }]
63
+ }
64
+
65
+ const formatObject = (o, model, config) => span("",
66
+ "{",
67
+ ["ol", { style: styles.list }, ...Object.keys(o).map(prop =>
68
+ ["li", { style: styles.listItem }, span(styles.property, prop), ": ", format(o[prop], config)])
69
+ ],
70
+ "}"
71
+ )
72
+
73
+ const formatModel = model => {
74
+ const
75
+ cfg = { isModelDefinition: true },
76
+ def = model.definition,
77
+ formatList = (list, map) => list.flatMap(e => [map(e), ", "]).slice(0, -1);
78
+ let parts = []
79
+
80
+ if (is(BasicModel, model)) parts = [format(def, cfg)]
81
+ if (is(ArrayModel, model)) parts = ["Array of ", format(def, cfg)]
82
+ if (is(SetModel, model)) parts = ["Set of ", format(def, cfg)]
83
+ if (is(MapModel, model)) parts = ["Map of ", format(def.key, cfg), " : ", format(def.value, cfg)]
84
+ if (is(FunctionModel, model)) {
85
+ parts = ["Function(", ...formatList(def.arguments, arg => format(arg, cfg)), ")"]
86
+ if ("return" in def) parts.push(" => ", format(def.return, cfg))
87
+ }
88
+
89
+ if (model.assertions.length > 0) {
90
+ parts.push("\n(assertions: ", ...formatList(model.assertions, f => ["object", { object: f }]), ")")
91
+ }
92
+
93
+ return span(styles.model, ...parts)
94
+ }
95
+
96
+ const ModelFormatter = {
97
+ header(x, config = {}) {
98
+ if (x === Any)
99
+ return span(styles.model, "Any")
100
+
101
+ if (is(Any.remaining, x))
102
+ return span(styles.model, "...", format(x.definition, { isModelDefinition: true }))
103
+
104
+ if (is(ObjectModel, x))
105
+ return span(styles.model, x.name)
106
+
107
+ if (is(Model, x)) {
108
+ return formatModel(x)
109
+ }
110
+
111
+ if (config.isModelDefinition && isPlainObject(x))
112
+ return format(x, config)
113
+
114
+ return null;
115
+ },
116
+ hasBody(x) {
117
+ return is(ObjectModel, x)
118
+ },
119
+ body(model) {
120
+ return span("",
121
+ "{",
122
+ ["ol", { style: styles.list }, ...Object.keys(model.definition).map(prop => {
123
+ const isPrivate = model.conventionForPrivate(prop)
124
+ const isConstant = model.conventionForConstant(prop)
125
+ const hasDefault = model.default && has(model.default, prop);
126
+ let style = styles.property;
127
+
128
+ if (isPrivate) {
129
+ style = isConstant ? styles.privateConstant : styles.private
130
+ } else if (isConstant) {
131
+ style = styles.constant
132
+ }
133
+
134
+ return ["li", { style: styles.listItem },
135
+ span(style, prop), ": ", format(model.definition[prop], { isModelDefinition: true }),
136
+ hasDefault ? span(styles.proto, " = ", format(model.default[prop])) : ""
137
+ ]
138
+ })],
139
+ "}"
140
+ )
141
+ }
142
+ }
143
+
144
+ const ModelInstanceFormatter = {
145
+ header(x, config = {}) {
146
+ if (config.isInstanceProperty && isPlainObject(x)) {
147
+ return format(x, config)
148
+ }
149
+
150
+ const model = getModel(x);
151
+ if (is(Model, model)) {
152
+ const parts = is(ObjectModel, model) ? [model.name] : [["object", { object: x[_original] }], ` (${model.name})`];
153
+ return span(styles.instance, ...parts)
154
+ }
155
+
156
+ return null;
157
+ },
158
+ hasBody(x) {
159
+ return x && is(ObjectModel, getModel(x))
160
+ },
161
+ body(x) {
162
+ const model = getModel(x)
163
+ const o = x[_original] || x;
164
+ return span("",
165
+ "{",
166
+ [
167
+ "ol",
168
+ { style: styles.list },
169
+ ...Object.keys(o).map(prop => {
170
+ const isPrivate = model.conventionForPrivate(prop)
171
+ const isConstant = model.conventionForConstant(prop)
172
+ const isDeclared = prop in model.definition;
173
+ let style = styles.property;
174
+
175
+ if (!isDeclared) {
176
+ style = styles.undeclared
177
+ } else if (isPrivate) {
178
+ style = isConstant ? styles.privateConstant : styles.private
179
+ } else if (isConstant) {
180
+ style = styles.constant
181
+ }
182
+
183
+ return ["li", { style: styles.listItem },
184
+ span(style, prop), ": ", format(o[prop], { isInstanceProperty: true })
185
+ ]
186
+ }),
187
+ ["li", { style: styles.listItem },
188
+ span(styles.proto, "__proto__", ": ", ["object", { object: getProto(x) }])
189
+ ]
190
+ ],
191
+ "}"
192
+ )
193
+ }
194
+ }
195
+
196
+ if (typeof window !== "undefined") {
197
+ window.devtoolsFormatters = (window.devtoolsFormatters || [])
198
+ .concat(ModelFormatter, ModelInstanceFormatter);
199
199
  }
@@ -0,0 +1,24 @@
1
+ import { FromDefinition, ModelDefinition } from "../types/definitions";
2
+ import { Model } from "./object-model";
3
+
4
+ type FromArgsDef<T> = T extends [infer A, ...infer Rest] ? [FromDefinition<A>, ...FromArgsDef<Rest>] : T
5
+
6
+ export type FunctionSignature<Args extends any[], Return> = {
7
+ (...args: FromArgsDef<Args>): FromDefinition<Return>
8
+ }
9
+
10
+ export interface FunctionModel<Args extends ModelDefinition[], Return extends ModelDefinition> extends Model<{ arguments: Args, return: Return }> {
11
+ (): FunctionSignature<Args, Return>;
12
+ (fn: FunctionSignature<Args, Return>): FunctionSignature<Args, Return>;
13
+ new (fn: FunctionSignature<Args, Return>): FunctionSignature<Args, Return>;
14
+ definition: { arguments: Args, return: Return };
15
+
16
+ return<R extends ModelDefinition>(returnValueDefinition: R): FunctionModel<Args, R>;
17
+ }
18
+
19
+ export interface FunctionModelConstructor {
20
+ <Args extends ModelDefinition[]>(...argumentsDefinitions: Args): FunctionModel<Args, any>;
21
+ new<Args extends ModelDefinition[]>(...argumentsDefinitions: Args): FunctionModel<Args, any>;
22
+ }
23
+
24
+ export const FunctionModel: FunctionModelConstructor;