arrmatura 6.3.1 → 6.3.2
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.
- package/README.md +6 -10
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/docs/manual.md +84 -66
- package/index.ts +5 -4
- package/package.json +10 -4
- package/src/core/{Entitron.ts → Arrmatron.ts} +147 -136
- package/src/core/Component.ts +44 -21
- package/src/core/ManifestNode.ts +36 -25
- package/src/core/resolveExpression.ts +9 -4
- package/src/registry/composition.ts +9 -8
- package/src/registry/conditionals.ts +10 -8
- package/src/registry/connector.ts +47 -0
- package/src/registry/elementary.ts +7 -6
- package/src/registry/fragment.ts +7 -6
- package/src/registry/index.ts +21 -25
- package/src/registry/iterations.ts +30 -18
- package/src/registry/root.ts +8 -6
- package/src/registry/routing.ts +6 -5
- package/src/registry/selection.ts +11 -9
- package/src/registry/slot.ts +8 -7
- package/src/utils/FingerprintMashine.ts +128 -0
- package/src/utils/applyStateChangedToImpl.ts +18 -0
- package/src/utils/compileExpression.ts +136 -29
- package/src/utils/objectFingerprint.spec.ts +70 -0
- package/types.ts +38 -29
package/src/registry/slot.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ManifestNode } from "../core/ManifestNode";
|
|
2
|
-
import { Entitron } from "../core/Entitron";
|
|
3
1
|
import type { XmlNode } from "ultimus/types";
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
import { Arrmatron } from "../core/Arrmatron";
|
|
4
|
+
import { ManifestNode } from "../core/ManifestNode";
|
|
5
|
+
|
|
5
6
|
import { Composite } from "./composition";
|
|
6
7
|
|
|
7
|
-
class Slot extends
|
|
8
|
+
class Slot extends Arrmatron {
|
|
8
9
|
get displayName(): string {
|
|
9
10
|
return "🔸";
|
|
10
11
|
}
|
|
@@ -17,7 +18,7 @@ class Slot extends Entitron {
|
|
|
17
18
|
return this.compositeScope.slotContent((this.manifest as CSlotNode).key);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
get recontentScope():
|
|
21
|
+
get recontentScope(): Arrmatron {
|
|
21
22
|
return this.compositeScope.scope;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -26,8 +27,8 @@ export class CSlotNode extends ManifestNode {
|
|
|
26
27
|
key?: string;
|
|
27
28
|
|
|
28
29
|
constructor(x: XmlNode) {
|
|
29
|
-
super();
|
|
30
|
-
this.key = x.attrs?.
|
|
30
|
+
super(x.id);
|
|
31
|
+
this.key = x.attrs?.Key as string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
get EntitronConstructor() {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const CACHE = new Map();
|
|
2
|
+
|
|
3
|
+
function upnum(r: number, n: number) {
|
|
4
|
+
const rr = ((r << 5) - r) + n;
|
|
5
|
+
return rr & rr;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const COLON_NUM = ":".charCodeAt(0);
|
|
9
|
+
|
|
10
|
+
function stringHash(x: string) {
|
|
11
|
+
let h = 0;
|
|
12
|
+
if (CACHE.has(x)) {
|
|
13
|
+
h = CACHE.get(x) as number;
|
|
14
|
+
} else {
|
|
15
|
+
for (let i = 0; i < x.length; i++) {
|
|
16
|
+
h = upnum(h, x.charCodeAt(i));
|
|
17
|
+
}
|
|
18
|
+
CACHE.set(x, h);
|
|
19
|
+
}
|
|
20
|
+
return h;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class FingerprintMashine {
|
|
24
|
+
r = 0;
|
|
25
|
+
x?: any;
|
|
26
|
+
$path?: Set<unknown>;
|
|
27
|
+
|
|
28
|
+
get path() {
|
|
29
|
+
return this.$path ?? (this.$path = new Set());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
upNumber(n: number) {
|
|
33
|
+
return (this.r = upnum(this.r, n));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
upString(x: string) {
|
|
37
|
+
return this.upNumber(stringHash(x))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
upObject(x) {
|
|
41
|
+
|
|
42
|
+
if (x.$hash) return x.$hash;
|
|
43
|
+
|
|
44
|
+
if (x instanceof Date) return this.upNumber(x.getTime());
|
|
45
|
+
|
|
46
|
+
if (this.path.size > 10 || this.path.has(x)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.path.add(x);
|
|
51
|
+
|
|
52
|
+
if (x instanceof Map) {
|
|
53
|
+
this.upString("$$$Map$$$");
|
|
54
|
+
x.forEach((v, k) => {
|
|
55
|
+
this.upString(k);
|
|
56
|
+
this.upNumber(COLON_NUM);
|
|
57
|
+
this.upAny(v);
|
|
58
|
+
})
|
|
59
|
+
} else if (x instanceof Set) {
|
|
60
|
+
this.upString("$$$Set$$$");
|
|
61
|
+
x.forEach((v) => this.upAny(v))
|
|
62
|
+
} else if (Array.isArray(x)) {
|
|
63
|
+
this.upString("$$$Array$$$");
|
|
64
|
+
x.forEach((v) => this.upAny(v))
|
|
65
|
+
} else {
|
|
66
|
+
this.upString("$$$Object$$$");
|
|
67
|
+
for (const k in x) {
|
|
68
|
+
if (Object.prototype.hasOwnProperty.call(x, k) && k[0] !== '$') {
|
|
69
|
+
this.upString(k)
|
|
70
|
+
this.upNumber(COLON_NUM);
|
|
71
|
+
this.upAny(x[k]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.path.delete(x);
|
|
77
|
+
|
|
78
|
+
return this.r;
|
|
79
|
+
}
|
|
80
|
+
upAny(x: any): void {
|
|
81
|
+
|
|
82
|
+
if (x == null) {
|
|
83
|
+
this.upString(String(x));
|
|
84
|
+
} else if (typeof x === 'number') {
|
|
85
|
+
if (isNaN(x)) {
|
|
86
|
+
this.upString(`$$$NaN$$$`);
|
|
87
|
+
} else {
|
|
88
|
+
this.upNumber(x);
|
|
89
|
+
}
|
|
90
|
+
} else if (typeof x === 'object') {
|
|
91
|
+
this.upObject(x);
|
|
92
|
+
} else if (typeof x === 'string') {
|
|
93
|
+
this.upString(x);
|
|
94
|
+
} else if (typeof x === 'boolean') {
|
|
95
|
+
this.upString(`$$$Boolean$$$${x}`);
|
|
96
|
+
} else if (typeof x === 'function') {
|
|
97
|
+
// this.upString(`$$$Function$$$${x}`);
|
|
98
|
+
} else {
|
|
99
|
+
this.upString(String(x))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
do(x: any) {
|
|
104
|
+
|
|
105
|
+
if (x == null) return x;
|
|
106
|
+
|
|
107
|
+
if (typeof x !== "object") return x;
|
|
108
|
+
|
|
109
|
+
if (Object.isFrozen(x)) return x;
|
|
110
|
+
|
|
111
|
+
if (x.$hash) return x.$hash;
|
|
112
|
+
|
|
113
|
+
this.r = 0;
|
|
114
|
+
this.x = x;
|
|
115
|
+
if (this.$path) {
|
|
116
|
+
this.$path?.clear();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.upObject(x);
|
|
120
|
+
|
|
121
|
+
return this.r;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const FM = new FingerprintMashine();
|
|
127
|
+
|
|
128
|
+
export const objectFingerprint = (x: any) => FM.do(x)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { capitalize } from "ultimus";
|
|
2
|
+
|
|
3
|
+
import type { IComponent } from "../../types";
|
|
4
|
+
|
|
5
|
+
export function applyStateChangedToImpl(changes: Map<string, unknown>, impl: IComponent) {
|
|
6
|
+
if (impl.__stateChanged) {
|
|
7
|
+
impl.__stateChanged(changes);
|
|
8
|
+
} else {
|
|
9
|
+
changes.forEach((v, k) => {
|
|
10
|
+
const setter = impl[`set${capitalize(k)}`];
|
|
11
|
+
if (typeof setter === "function") {
|
|
12
|
+
setter.call(impl, v);
|
|
13
|
+
} else {
|
|
14
|
+
impl[k] = v;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,8 +1,88 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { IArrmatron } from "arrmatura/types";
|
|
2
|
+
import { scalarParse, gettus } from "ultimus";
|
|
2
3
|
import type { ExpressionText, Fn, StringHash } from "ultimus/types";
|
|
3
|
-
import type { IEntitron } from "arrmatura/types";
|
|
4
4
|
|
|
5
|
-
type PipeFn = (c:
|
|
5
|
+
type PipeFn = (c: IArrmatron, argValue: unknown) => unknown;
|
|
6
|
+
|
|
7
|
+
// function tokenize(expression) {
|
|
8
|
+
// return expression.split(/[\(\)\<\>=\?!:']+/g);
|
|
9
|
+
// }
|
|
10
|
+
// class Node {
|
|
11
|
+
// constructor(public type, public value: string | null = null, public left = null, public right = null) {
|
|
12
|
+
// }
|
|
13
|
+
// }
|
|
14
|
+
|
|
15
|
+
// function parse(tokens) {
|
|
16
|
+
// let current = 0;
|
|
17
|
+
|
|
18
|
+
// function walk(priority = 0) {
|
|
19
|
+
// const token = tokens[current++];
|
|
20
|
+
// const nextToken = tokens[current];
|
|
21
|
+
|
|
22
|
+
// if (priority >= 0 && token === '(') {
|
|
23
|
+
// const node = walk();
|
|
24
|
+
// current++;
|
|
25
|
+
// return node;
|
|
26
|
+
// }
|
|
27
|
+
|
|
28
|
+
// if (priority >= 0 && ['>', '<', '==', '!=', '&&', '||', '??'].includes(nextToken)) {
|
|
29
|
+
// return new Node('binary', nextToken, walk(), walk());
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
// if (priority >= 0 && ['>', '<', '==', '!=', '&&', '||', '??'].includes(nextToken)) {
|
|
33
|
+
// return new Node('binary', nextToken, walk(), walk());
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// if (priority >= 0 && ['>', '<', '==', '!=', '&&', '||', '??'].includes(nextToken)) {
|
|
37
|
+
// return new Node('binary', nextToken, walk(), walk());
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
// if (priority >= 0 && nextToken === '?') {
|
|
41
|
+
// const node = new Node('coalesce', walk());
|
|
42
|
+
// if (tokens[current] !== ':') {
|
|
43
|
+
// throw new TypeError('Expected colon after "?" in coalesce operation');
|
|
44
|
+
// }
|
|
45
|
+
// current++;
|
|
46
|
+
// node.right = new Node('colon', token, walk(), walk());
|
|
47
|
+
// return node;
|
|
48
|
+
// }
|
|
49
|
+
// return new Node('operand', (token));;
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// return walk();
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// function evaluate(node) {
|
|
56
|
+
// switch (node.type) {
|
|
57
|
+
// case 'number':
|
|
58
|
+
// return node.value;
|
|
59
|
+
// case 'operator':
|
|
60
|
+
// switch (node.value) {
|
|
61
|
+
// case '>':
|
|
62
|
+
// return evaluate(node.left) > evaluate(node.right);
|
|
63
|
+
// case '<':
|
|
64
|
+
// return evaluate(node.left) < evaluate(node.right);
|
|
65
|
+
// case '==':
|
|
66
|
+
// return evaluate(node.left) == evaluate(node.right);
|
|
67
|
+
// case '!=':
|
|
68
|
+
// return evaluate(node.left) != evaluate(node.right);
|
|
69
|
+
// default:
|
|
70
|
+
// throw new TypeError(`Unknown operator: ${node.value}`);
|
|
71
|
+
// }
|
|
72
|
+
// case 'coalesce':
|
|
73
|
+
// const condition = evaluate(node.left);
|
|
74
|
+
// return condition ? evaluate(node.middle) : evaluate(node.right);
|
|
75
|
+
// default:
|
|
76
|
+
// throw new TypeError(`Unknown node type: ${node.type}`);
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
|
|
80
|
+
// // Example usage
|
|
81
|
+
// const expression = '1 < 2 ? 3 : 4';
|
|
82
|
+
// const tokens = tokenize(expression);
|
|
83
|
+
// const ast = parse(tokens);
|
|
84
|
+
// console.log(evaluate(ast)); // Outputs: 3
|
|
85
|
+
|
|
6
86
|
|
|
7
87
|
const OP_SHORTCUTS: StringHash = {
|
|
8
88
|
"&&": "|and:",
|
|
@@ -19,24 +99,31 @@ const OP_SHORTCUTS: StringHash = {
|
|
|
19
99
|
|
|
20
100
|
const withNoPipes = (_: unknown, v: unknown) => v;
|
|
21
101
|
|
|
102
|
+
const isConstKey = (key: string) => "'+-0123456789".includes(key[0]) || key === 'true' || key === 'false';
|
|
103
|
+
|
|
22
104
|
export function compileConstant(a: string) {
|
|
23
105
|
const vv = scalarParse(a[0] === "'" ? a.slice(1, a.length - 1) : a);
|
|
24
106
|
return () => vv;
|
|
25
107
|
}
|
|
26
108
|
|
|
27
109
|
const compilePropGet = (key: string) => {
|
|
28
|
-
return (c:
|
|
110
|
+
return (c: IArrmatron) => c.getFromScope(key);
|
|
29
111
|
};
|
|
30
112
|
|
|
31
|
-
const compileArgument = (a: string) => {
|
|
32
|
-
if (a[0]
|
|
113
|
+
const compileArgument = (a: string, strings?: string[]) => {
|
|
114
|
+
if (a[0] === "!") {
|
|
115
|
+
const getter = compilePropGet(a.slice(1));
|
|
116
|
+
return (c: IArrmatron) => !getter(c);
|
|
117
|
+
};
|
|
33
118
|
|
|
34
|
-
if (a
|
|
35
|
-
const
|
|
36
|
-
return (
|
|
119
|
+
if (a[0] === "#" && a[1] === "#") {
|
|
120
|
+
const vv = strings?.[Number(a.slice(2))];
|
|
121
|
+
return () => vv;
|
|
37
122
|
}
|
|
38
123
|
|
|
39
|
-
return
|
|
124
|
+
if (isConstKey(a)) return compileConstant(a);
|
|
125
|
+
|
|
126
|
+
return compilePropGet(a);
|
|
40
127
|
};
|
|
41
128
|
|
|
42
129
|
const splitExpression = (v: ExpressionText) =>
|
|
@@ -49,22 +136,37 @@ function withPipes(pipes: string[]) {
|
|
|
49
136
|
if (!pipes.length) return withNoPipes;
|
|
50
137
|
|
|
51
138
|
const compiledPipes = pipes.map((key) => {
|
|
52
|
-
const [
|
|
139
|
+
const strings: string[] = [];
|
|
140
|
+
|
|
141
|
+
const expr = key.replaceAll(/'[^']*'/g, (s) => {
|
|
142
|
+
strings.push(s.slice(1, -1))
|
|
143
|
+
return `##${strings.length - 1}`
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const [pipeId, ...args] = expr.split(":").map((s) => s.trim());
|
|
53
147
|
return {
|
|
54
148
|
pipeId,
|
|
55
|
-
args: args.map((a) => compileArgument(a)),
|
|
149
|
+
args: args.map((a) => compileArgument(a, strings)),
|
|
56
150
|
};
|
|
57
151
|
});
|
|
58
152
|
|
|
59
|
-
return (c:
|
|
153
|
+
return (c: IArrmatron, initialValue: unknown) =>
|
|
60
154
|
compiledPipes.reduce((value, { pipeId, args }) => {
|
|
61
155
|
try {
|
|
62
156
|
const fn = c.platform.getFunction(pipeId);
|
|
63
157
|
if (typeof fn !== "function") {
|
|
64
158
|
throw new Error(`${fn != null ? "must be a function" : "not found"}`);
|
|
65
159
|
}
|
|
66
|
-
|
|
67
|
-
|
|
160
|
+
if (value instanceof Promise) {
|
|
161
|
+
return value.then((val) => {
|
|
162
|
+
const args0 = [val, ...args.map((fn) => fn(c))];
|
|
163
|
+
const result = fn.apply(c, args0);
|
|
164
|
+
return result;
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
const args0 = [value, ...args.map((fn) => fn(c))];
|
|
168
|
+
return fn.apply(c, args0);
|
|
169
|
+
}
|
|
68
170
|
} catch (ex) {
|
|
69
171
|
c.logError(`ERROR: Function ${pipeId || "<empty>"}`, ex);
|
|
70
172
|
return value;
|
|
@@ -74,35 +176,40 @@ function withPipes(pipes: string[]) {
|
|
|
74
176
|
|
|
75
177
|
function withHardValue(hardValue: ExpressionText, pipec: PipeFn): PipeFn {
|
|
76
178
|
if (!hardValue || hardValue === '*') return pipec;
|
|
77
|
-
|
|
78
|
-
|
|
179
|
+
|
|
180
|
+
if (hardValue.startsWith('*.')) {
|
|
181
|
+
const propKey = hardValue.slice(2)
|
|
182
|
+
return (c: IArrmatron, ini: any) => pipec(c, gettus(ini, propKey));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const iniFn = isConstKey(hardValue) ? compileConstant(hardValue) : compilePlaceholder(hardValue);
|
|
186
|
+
|
|
187
|
+
return (c: IArrmatron) => pipec(c, iniFn(c));
|
|
79
188
|
}
|
|
80
189
|
|
|
81
190
|
export function compilePipeExpression(v: ExpressionText) {
|
|
191
|
+
if (v.endsWith(')')) {
|
|
192
|
+
const parts = v.slice(0, -1).split('(')
|
|
193
|
+
return compilePipeExpression(`${parts[0]}=${parts[1]}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
82
196
|
const [_key, ...pipes] = splitExpression(v);
|
|
83
197
|
const [key, hardValue] = _key.split("=").map((s) => s.trim());
|
|
198
|
+
|
|
84
199
|
return { key, pipec: withHardValue(hardValue, withPipes(pipes)) };
|
|
85
200
|
}
|
|
86
201
|
|
|
87
202
|
export function compilePlaceholder(expr: string): Fn {
|
|
88
203
|
const [key, ...pipesExpressions] = splitExpression(expr);
|
|
89
|
-
const pipec = withPipes(pipesExpressions);
|
|
90
204
|
|
|
91
|
-
|
|
205
|
+
const pipec = withPipes(pipesExpressions);
|
|
92
206
|
|
|
93
|
-
|
|
94
|
-
fn = compileConstant(key);
|
|
95
|
-
} else {
|
|
96
|
-
if (key[0] !== "@") {
|
|
97
|
-
console.warn(`Invalid placeholder: missed @-prefix: "${expr}"`);
|
|
98
|
-
}
|
|
99
|
-
fn = compileArgument(key[0] === "@" ? key : `@${key}`);
|
|
100
|
-
}
|
|
207
|
+
const fn = compileArgument(key);
|
|
101
208
|
|
|
102
|
-
return (c) => pipec(c as
|
|
209
|
+
return (c) => pipec(c as IArrmatron, fn(c));
|
|
103
210
|
}
|
|
104
211
|
|
|
105
212
|
export function compileJsExpression(x: string) {
|
|
106
|
-
return new Function("c", x.replaceAll(/[^'a-z]([a-z][a-z0-9
|
|
213
|
+
return new Function("c", x.replaceAll(/[^'a-z]([a-z][a-z0-9-.]+)/gi, 'c.prop("$1")'));
|
|
107
214
|
}
|
|
108
215
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { objectFingerprint } from './FingerprintMashine';
|
|
2
|
+
|
|
3
|
+
describe('objectFingerprint', () => {
|
|
4
|
+
it('should return 0 for null or undefined input', () => {
|
|
5
|
+
expect(objectFingerprint(null)).toBe(null);
|
|
6
|
+
expect(objectFingerprint(undefined)).toBe(undefined);
|
|
7
|
+
expect(objectFingerprint(0)).toBe(0);
|
|
8
|
+
expect(objectFingerprint("")).toBe("");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should return the same hash value for the same input string, regardless of the case of its characters', () => {
|
|
12
|
+
const str1 = 'Hello World';
|
|
13
|
+
const str2 = 'Hello World';
|
|
14
|
+
const str3 = 'Hello world';
|
|
15
|
+
expect(objectFingerprint(str1)).toBe(objectFingerprint(str2));
|
|
16
|
+
expect(objectFingerprint(str1)).not.toBe(objectFingerprint(str3));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return a unique hash value for different input objects', () => {
|
|
20
|
+
const obj1 = { foo: 'bar' };
|
|
21
|
+
const obj2 = { bar: 'baz' };
|
|
22
|
+
expect(objectFingerprint(obj1)).not.toBe(objectFingerprint(obj2));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return the same hash value for the same input object, regardless of the order of its properties', () => {
|
|
26
|
+
const obj0 = { foo: 'bar', baz: 'qux' };
|
|
27
|
+
const obj1 = { foo: 'bar', baz: 'qux' };
|
|
28
|
+
const obj2 = { baz: 'qux', foo: 'bar' };
|
|
29
|
+
expect(objectFingerprint(obj1)).toBe(objectFingerprint(obj0));
|
|
30
|
+
expect(objectFingerprint(obj1)).not.toBe(objectFingerprint(obj2));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return the same hash value for the same input array, regardless of the order of its elements', () => {
|
|
34
|
+
const arr0 = [1, 2, 3];
|
|
35
|
+
const arr1 = [1, 2, 3];
|
|
36
|
+
const arr2 = [3, 2, 1];
|
|
37
|
+
expect(objectFingerprint(arr1)).toBe(objectFingerprint(arr0));
|
|
38
|
+
expect(objectFingerprint(arr1)).not.toBe(objectFingerprint(arr2));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return the same hash value for the same input map, regardless of the order of its entries', () => {
|
|
42
|
+
const map0 = new Map([[1, 'one'], [2, 'two']]);
|
|
43
|
+
const map1 = new Map([[1, 'one'], [2, 'two']]);
|
|
44
|
+
const map2 = new Map([[2, 'two'], [1, 'one']]);
|
|
45
|
+
expect(objectFingerprint(map1)).toBe(objectFingerprint(map0));
|
|
46
|
+
expect(objectFingerprint(map1)).not.toBe(objectFingerprint(map2));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return the same hash value for the same input set, regardless of the order of its elements', () => {
|
|
50
|
+
const set0 = new Set([1, 2, 3]);
|
|
51
|
+
const set1 = new Set([1, 2, 3]);
|
|
52
|
+
const set2 = new Set([3, 2, 1]);
|
|
53
|
+
expect(objectFingerprint(set1)).toBe(objectFingerprint(set0));
|
|
54
|
+
expect(objectFingerprint(set1)).not.toBe(objectFingerprint(set2));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
it('should return the same hash value for the same input number, regardless of its precision', () => {
|
|
59
|
+
const num1 = 3.14159;
|
|
60
|
+
const num2 = 3.14159;
|
|
61
|
+
const num3 = 3.14158;
|
|
62
|
+
expect(objectFingerprint(num1)).toBe(objectFingerprint(num2));
|
|
63
|
+
expect(objectFingerprint(num1)).not.toBe(objectFingerprint(num3));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return the same hash value for the same input boolean, regardless of its value', () => {
|
|
67
|
+
expect(objectFingerprint(true)).toBe(objectFingerprint(true));
|
|
68
|
+
expect(objectFingerprint(false)).toBe(objectFingerprint(false))
|
|
69
|
+
});
|
|
70
|
+
})
|
package/types.ts
CHANGED
|
@@ -1,64 +1,75 @@
|
|
|
1
1
|
import { Uid, Delta, Data, LogEntry, Hash, XmlNode } from "ultimus/types";
|
|
2
2
|
|
|
3
|
+
import { Arrmatron } from "./src/core/Arrmatron";
|
|
4
|
+
|
|
3
5
|
export type IComponent = {
|
|
4
|
-
|
|
5
|
-
done?: (c:
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// optional Lifecycle method called when the component is done.
|
|
7
|
+
done?: (c: IArrmatron) => void;
|
|
8
|
+
// optional Lifecycle method called during initialization.
|
|
9
|
+
|
|
10
|
+
init?: (c: IArrmatron) => Delta | null | undefined | unknown | void;
|
|
11
|
+
// optional Lifecycle method called when the state changes.
|
|
12
|
+
__stateChanged?: (changes: Map<string, unknown>) => void;
|
|
13
|
+
// state key/values
|
|
9
14
|
[key: string]: unknown;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
export type
|
|
17
|
+
export type IArrmatron = {
|
|
13
18
|
isDone: any;
|
|
14
19
|
readonly uid: Uid;
|
|
15
|
-
parent?:
|
|
16
|
-
children?: Map<Uid,
|
|
17
|
-
scope?:
|
|
18
|
-
component: IComponent;
|
|
19
|
-
root:
|
|
20
|
+
parent?: IArrmatron;
|
|
21
|
+
children?: Map<Uid, IArrmatron>;
|
|
22
|
+
scope?: IArrmatron;
|
|
23
|
+
$component: IComponent;
|
|
24
|
+
root: IArrmatron;
|
|
20
25
|
platform: IPlatform;
|
|
21
26
|
|
|
27
|
+
// state access
|
|
28
|
+
get: (propId: string) => unknown;
|
|
29
|
+
getFromScope: (key: string) => unknown;
|
|
22
30
|
up: (d: Delta | null | void | unknown, force?: boolean) => void;
|
|
31
|
+
|
|
23
32
|
touch: () => void;
|
|
24
33
|
emit: (key: string, arg1: Delta) => void;
|
|
25
|
-
res: (key: string | string[]) => Data;
|
|
26
|
-
log: (val: unknown, ...arg1: unknown[]) => void;
|
|
27
|
-
logError: (error: unknown, ...arg1: unknown[]) => void;
|
|
28
|
-
defer: (fn: () => void) => void;
|
|
29
|
-
get: (propId: string) => unknown;
|
|
30
34
|
|
|
35
|
+
// done life-cycle hook
|
|
31
36
|
done: () => void;
|
|
37
|
+
defer: (fn: () => void) => void;
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
// get registered arrmatron instance by reference. look up recurrsively from current to root scopes.
|
|
40
|
+
readonly refId?: string;
|
|
41
|
+
getByRef: (refId: string) => IArrmatron | undefined;
|
|
42
|
+
|
|
43
|
+
log: (val: unknown, ...arg1: unknown[]) => void;
|
|
44
|
+
logError: (error: unknown, ...arg1: unknown[]) => void;
|
|
45
|
+
toast(t: LogEntry): void;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export type IManifestNode = {
|
|
40
|
-
connectors?: Map<string, (c:
|
|
49
|
+
connectors?: Map<string, (c: IArrmatron, x: unknown) => unknown>;
|
|
41
50
|
refId?: string;
|
|
42
51
|
readonly uid: Uid;
|
|
43
52
|
getSubNodes(platform: IPlatform): Map<Uid, IManifestNode> | undefined;
|
|
44
|
-
resolveProps: (c:
|
|
45
|
-
resolveInitialProps: (c:
|
|
53
|
+
resolveProps: (c: IArrmatron) => Delta | Promise<unknown> | undefined;
|
|
54
|
+
resolveInitialProps: (c: IArrmatron) => Hash;
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
createArrmatron: (platform: IPlatform, parent?: Arrmatron, scope?: Arrmatron) => Arrmatron;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export type IPlatform = {
|
|
51
60
|
log(arg0: LogEntry): void;
|
|
61
|
+
toast(t: LogEntry, ctx: IArrmatron): void;
|
|
52
62
|
getCompiledNodes(tag: string): Map<Uid, IManifestNode>;
|
|
53
63
|
getCompiledNodes(tag: XmlNode): IManifestNode;
|
|
54
64
|
getCompiledNodes(tag: XmlNode[]): Map<Uid, IManifestNode>;
|
|
65
|
+
updateResources(delta: any): void;
|
|
55
66
|
getResource(key: string | string[]): Data;
|
|
56
67
|
getFunction(id: string): (...args: unknown[]) => unknown;
|
|
57
|
-
redraw(source:
|
|
58
|
-
createComponent(def: IComponentDescriptor | string, initials: Hash, ctx:
|
|
68
|
+
redraw(source: IArrmatron, root: IArrmatron): void;
|
|
69
|
+
createComponent(def: IComponentDescriptor | string, initials: Hash, ctx: IArrmatron): IComponent | Hash;
|
|
59
70
|
}
|
|
60
71
|
|
|
61
|
-
export type ComponentConstructor = new (initials: Hash, c:
|
|
72
|
+
export type ComponentConstructor = new (initials: Hash, c: IArrmatron) => IComponent;
|
|
62
73
|
|
|
63
74
|
export type IComponentDescriptor = {
|
|
64
75
|
tag?: string;
|
|
@@ -68,6 +79,4 @@ export type IComponentDescriptor = {
|
|
|
68
79
|
native?: boolean;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
export type EclecticTypeInfo = string | IComponentDescriptor | ComponentConstructor;
|
|
72
|
-
|
|
73
82
|
export type EContent = Map<Uid, IManifestNode>;
|