arrmatura 6.3.2 → 6.3.3
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 +9 -15
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +4 -4
- package/docs/components.md +89 -0
- package/docs/{hello.md → getstarted.md} +21 -4
- package/docs/manual.md +7 -310
- package/docs/templates.md +222 -0
- package/index.ts +4 -4
- package/package.json +8 -6
- package/src/core/Arrmatron.ts +47 -41
- package/src/core/Component.ts +19 -21
- package/src/core/ManifestNode.ts +22 -19
- package/src/core/resolveExpression.ts +7 -3
- package/src/registry/{iterations.ts → Iterative.ts} +11 -16
- package/src/registry/composition.ts +7 -4
- package/src/registry/conditionals.ts +8 -5
- package/src/registry/connector.ts +7 -6
- package/src/registry/index.ts +2 -5
- package/src/registry/root.ts +1 -1
- package/src/registry/routing.ts +6 -1
- package/src/utils/FingerprintMashine.ts +18 -20
- package/src/utils/compileExpression.ts +11 -90
- package/src/utils/narrowData.ts +15 -0
- package/types.ts +35 -27
- package/docs/glossary.md +0 -19
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const CACHE = new Map();
|
|
2
2
|
|
|
3
3
|
function upnum(r: number, n: number) {
|
|
4
|
-
const rr = (
|
|
4
|
+
const rr = (r << 5) - r + n;
|
|
5
5
|
return rr & rr;
|
|
6
6
|
}
|
|
7
7
|
|
|
@@ -34,12 +34,11 @@ export class FingerprintMashine {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
upString(x: string) {
|
|
37
|
-
return this.upNumber(stringHash(x))
|
|
37
|
+
return this.upNumber(stringHash(x));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
upObject(x) {
|
|
41
|
-
|
|
42
|
-
if (x.$hash) return x.$hash;
|
|
41
|
+
if (x.uid) return x.uid;
|
|
43
42
|
|
|
44
43
|
if (x instanceof Date) return this.upNumber(x.getTime());
|
|
45
44
|
|
|
@@ -55,18 +54,18 @@ export class FingerprintMashine {
|
|
|
55
54
|
this.upString(k);
|
|
56
55
|
this.upNumber(COLON_NUM);
|
|
57
56
|
this.upAny(v);
|
|
58
|
-
})
|
|
57
|
+
});
|
|
59
58
|
} else if (x instanceof Set) {
|
|
60
59
|
this.upString("$$$Set$$$");
|
|
61
|
-
x.forEach((v) => this.upAny(v))
|
|
60
|
+
x.forEach((v) => this.upAny(v));
|
|
62
61
|
} else if (Array.isArray(x)) {
|
|
63
62
|
this.upString("$$$Array$$$");
|
|
64
|
-
x.forEach((v) => this.upAny(v))
|
|
63
|
+
x.forEach((v) => this.upAny(v));
|
|
65
64
|
} else {
|
|
66
65
|
this.upString("$$$Object$$$");
|
|
67
66
|
for (const k in x) {
|
|
68
|
-
if (Object.prototype.hasOwnProperty.call(x, k) && k[0] !==
|
|
69
|
-
this.upString(k)
|
|
67
|
+
if (Object.prototype.hasOwnProperty.call(x, k) && k[0] !== "$") {
|
|
68
|
+
this.upString(k);
|
|
70
69
|
this.upNumber(COLON_NUM);
|
|
71
70
|
this.upAny(x[k]);
|
|
72
71
|
}
|
|
@@ -78,37 +77,37 @@ export class FingerprintMashine {
|
|
|
78
77
|
return this.r;
|
|
79
78
|
}
|
|
80
79
|
upAny(x: any): void {
|
|
81
|
-
|
|
82
80
|
if (x == null) {
|
|
83
81
|
this.upString(String(x));
|
|
84
|
-
} else if (typeof x ===
|
|
82
|
+
} else if (typeof x === "number") {
|
|
85
83
|
if (isNaN(x)) {
|
|
86
84
|
this.upString(`$$$NaN$$$`);
|
|
87
85
|
} else {
|
|
88
86
|
this.upNumber(x);
|
|
89
87
|
}
|
|
90
|
-
} else if (typeof x ===
|
|
88
|
+
} else if (typeof x === "object") {
|
|
91
89
|
this.upObject(x);
|
|
92
|
-
} else if (typeof x ===
|
|
90
|
+
} else if (typeof x === "string") {
|
|
93
91
|
this.upString(x);
|
|
94
|
-
} else if (typeof x ===
|
|
92
|
+
} else if (typeof x === "boolean") {
|
|
95
93
|
this.upString(`$$$Boolean$$$${x}`);
|
|
96
|
-
} else if (typeof x ===
|
|
94
|
+
} else if (typeof x === "function") {
|
|
97
95
|
// this.upString(`$$$Function$$$${x}`);
|
|
98
96
|
} else {
|
|
99
|
-
this.upString(String(x))
|
|
97
|
+
this.upString(String(x));
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
do(x: any) {
|
|
104
|
-
|
|
105
102
|
if (x == null) return x;
|
|
106
103
|
|
|
107
104
|
if (typeof x !== "object") return x;
|
|
108
105
|
|
|
109
106
|
if (Object.isFrozen(x)) return x;
|
|
110
107
|
|
|
111
|
-
if (x
|
|
108
|
+
if (x.uid) return x.uid;
|
|
109
|
+
|
|
110
|
+
if (x.$ctx) return x.$ctx.uid;
|
|
112
111
|
|
|
113
112
|
this.r = 0;
|
|
114
113
|
this.x = x;
|
|
@@ -120,9 +119,8 @@ export class FingerprintMashine {
|
|
|
120
119
|
|
|
121
120
|
return this.r;
|
|
122
121
|
}
|
|
123
|
-
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
const FM = new FingerprintMashine();
|
|
127
125
|
|
|
128
|
-
export const objectFingerprint = (x: any) => FM.do(x)
|
|
126
|
+
export const objectFingerprint = (x: any) => FM.do(x);
|
|
@@ -4,86 +4,6 @@ import type { ExpressionText, Fn, StringHash } from "ultimus/types";
|
|
|
4
4
|
|
|
5
5
|
type PipeFn = (c: IArrmatron, argValue: unknown) => unknown;
|
|
6
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
|
-
|
|
86
|
-
|
|
87
7
|
const OP_SHORTCUTS: StringHash = {
|
|
88
8
|
"&&": "|and:",
|
|
89
9
|
"==": "|equals:",
|
|
@@ -99,7 +19,8 @@ const OP_SHORTCUTS: StringHash = {
|
|
|
99
19
|
|
|
100
20
|
const withNoPipes = (_: unknown, v: unknown) => v;
|
|
101
21
|
|
|
102
|
-
const isConstKey = (key: string) =>
|
|
22
|
+
const isConstKey = (key: string) =>
|
|
23
|
+
"'+-0123456789".includes(key[0]) || key === "true" || key === "false";
|
|
103
24
|
|
|
104
25
|
export function compileConstant(a: string) {
|
|
105
26
|
const vv = scalarParse(a[0] === "'" ? a.slice(1, a.length - 1) : a);
|
|
@@ -114,7 +35,7 @@ const compileArgument = (a: string, strings?: string[]) => {
|
|
|
114
35
|
if (a[0] === "!") {
|
|
115
36
|
const getter = compilePropGet(a.slice(1));
|
|
116
37
|
return (c: IArrmatron) => !getter(c);
|
|
117
|
-
}
|
|
38
|
+
}
|
|
118
39
|
|
|
119
40
|
if (a[0] === "#" && a[1] === "#") {
|
|
120
41
|
const vv = strings?.[Number(a.slice(2))];
|
|
@@ -128,6 +49,7 @@ const compileArgument = (a: string, strings?: string[]) => {
|
|
|
128
49
|
|
|
129
50
|
const splitExpression = (v: ExpressionText) =>
|
|
130
51
|
v
|
|
52
|
+
.trim()
|
|
131
53
|
.replace(/(&&|!=|==|>|<|\[|\]|\|\||\?\??)/g, (_, s) => `${OP_SHORTCUTS[s]}`)
|
|
132
54
|
.split("|")
|
|
133
55
|
.map((s) => s.trim());
|
|
@@ -139,8 +61,8 @@ function withPipes(pipes: string[]) {
|
|
|
139
61
|
const strings: string[] = [];
|
|
140
62
|
|
|
141
63
|
const expr = key.replaceAll(/'[^']*'/g, (s) => {
|
|
142
|
-
strings.push(s.slice(1, -1))
|
|
143
|
-
return `##${strings.length - 1}
|
|
64
|
+
strings.push(s.slice(1, -1));
|
|
65
|
+
return `##${strings.length - 1}`;
|
|
144
66
|
});
|
|
145
67
|
|
|
146
68
|
const [pipeId, ...args] = expr.split(":").map((s) => s.trim());
|
|
@@ -175,10 +97,10 @@ function withPipes(pipes: string[]) {
|
|
|
175
97
|
}
|
|
176
98
|
|
|
177
99
|
function withHardValue(hardValue: ExpressionText, pipec: PipeFn): PipeFn {
|
|
178
|
-
if (!hardValue || hardValue ===
|
|
100
|
+
if (!hardValue || hardValue === "it") return pipec;
|
|
179
101
|
|
|
180
|
-
if (hardValue.startsWith(
|
|
181
|
-
const propKey = hardValue.slice(
|
|
102
|
+
if (hardValue.startsWith("it.")) {
|
|
103
|
+
const propKey = hardValue.slice(3);
|
|
182
104
|
return (c: IArrmatron, ini: any) => pipec(c, gettus(ini, propKey));
|
|
183
105
|
}
|
|
184
106
|
|
|
@@ -188,8 +110,8 @@ function withHardValue(hardValue: ExpressionText, pipec: PipeFn): PipeFn {
|
|
|
188
110
|
}
|
|
189
111
|
|
|
190
112
|
export function compilePipeExpression(v: ExpressionText) {
|
|
191
|
-
if (v.endsWith(
|
|
192
|
-
const parts = v.slice(0, -1).split(
|
|
113
|
+
if (v.endsWith(")")) {
|
|
114
|
+
const parts = v.slice(0, -1).split("(");
|
|
193
115
|
return compilePipeExpression(`${parts[0]}=${parts[1]}`);
|
|
194
116
|
}
|
|
195
117
|
|
|
@@ -212,4 +134,3 @@ export function compilePlaceholder(expr: string): Fn {
|
|
|
212
134
|
export function compileJsExpression(x: string) {
|
|
213
135
|
return new Function("c", x.replaceAll(/[^'a-z]([a-z][a-z0-9-.]+)/gi, 'c.prop("$1")'));
|
|
214
136
|
}
|
|
215
|
-
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mapEntries } from "ultimus";
|
|
2
|
+
|
|
3
|
+
export const narrowData = (data: any) => {
|
|
4
|
+
if (!data) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
if (typeof data === "string") return data.split(",").map((id) => ({ id, name: id }));
|
|
8
|
+
if (Array.isArray(data)) return data;
|
|
9
|
+
if (typeof data[Symbol.iterator] === "function") return [...data];
|
|
10
|
+
if (typeof data === "object")
|
|
11
|
+
return mapEntries(data, (key, value) =>
|
|
12
|
+
typeof value === "object" ? { ...value, id: key } : { id: key, name: String(value) }
|
|
13
|
+
);
|
|
14
|
+
return [{ id: String(data), name: String(data) }];
|
|
15
|
+
};
|
package/types.ts
CHANGED
|
@@ -3,47 +3,51 @@ import { Uid, Delta, Data, LogEntry, Hash, XmlNode } from "ultimus/types";
|
|
|
3
3
|
import { Arrmatron } from "./src/core/Arrmatron";
|
|
4
4
|
|
|
5
5
|
export type IComponent = {
|
|
6
|
-
// optional Lifecycle method called when the component is done.
|
|
7
|
-
done?: (c: IArrmatron) => void;
|
|
8
6
|
// optional Lifecycle method called during initialization.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// optional Lifecycle method called when the state changes.
|
|
7
|
+
__init?: (c: IArrmatron) => Delta | null | undefined | unknown | void;
|
|
8
|
+
// optional method called when the state changes.
|
|
12
9
|
__stateChanged?: (changes: Map<string, unknown>) => void;
|
|
10
|
+
// optional method to get a state property by name.
|
|
11
|
+
__getStateProperty?: (propName: string) => any;
|
|
13
12
|
// state key/values
|
|
14
13
|
[key: string]: unknown;
|
|
15
|
-
}
|
|
14
|
+
};
|
|
16
15
|
|
|
17
|
-
export type
|
|
18
|
-
isDone: any;
|
|
19
|
-
readonly uid: Uid;
|
|
20
|
-
parent?: IArrmatron;
|
|
21
|
-
children?: Map<Uid, IArrmatron>;
|
|
22
|
-
scope?: IArrmatron;
|
|
23
|
-
$component: IComponent;
|
|
24
|
-
root: IArrmatron;
|
|
16
|
+
export type IComponentContext = {
|
|
25
17
|
platform: IPlatform;
|
|
26
18
|
|
|
27
19
|
// state access
|
|
28
20
|
get: (propId: string) => unknown;
|
|
29
|
-
getFromScope: (key: string) => unknown;
|
|
30
21
|
up: (d: Delta | null | void | unknown, force?: boolean) => void;
|
|
31
22
|
|
|
32
23
|
touch: () => void;
|
|
33
24
|
emit: (key: string, arg1: Delta) => void;
|
|
34
|
-
|
|
35
|
-
// done life-cycle hook
|
|
36
|
-
done: () => void;
|
|
37
25
|
defer: (fn: () => void) => void;
|
|
38
26
|
|
|
39
|
-
// get registered arrmatron instance by reference. look up recurrsively from current to root scopes.
|
|
40
27
|
readonly refId?: string;
|
|
41
|
-
getByRef: (refId: string) => IArrmatron | undefined;
|
|
42
28
|
|
|
43
29
|
log: (val: unknown, ...arg1: unknown[]) => void;
|
|
44
30
|
logError: (error: unknown, ...arg1: unknown[]) => void;
|
|
45
31
|
toast(t: LogEntry): void;
|
|
46
|
-
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type IArrmatron = IComponentContext & {
|
|
35
|
+
readonly uid: Uid;
|
|
36
|
+
parent?: IArrmatron;
|
|
37
|
+
children?: Map<Uid, Arrmatron>;
|
|
38
|
+
scope?: IArrmatron;
|
|
39
|
+
$component: IComponent;
|
|
40
|
+
root: IArrmatron;
|
|
41
|
+
|
|
42
|
+
getFromScope: (key: string) => unknown;
|
|
43
|
+
// get registered arrmatron instance by reference. look up recurrsively from current to root scopes.
|
|
44
|
+
getByRef: (refId: string) => IArrmatron | undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type IArrmatronRoot = IArrmatron & {
|
|
48
|
+
// done life-cycle hook
|
|
49
|
+
done: () => void;
|
|
50
|
+
};
|
|
47
51
|
|
|
48
52
|
export type IManifestNode = {
|
|
49
53
|
connectors?: Map<string, (c: IArrmatron, x: unknown) => unknown>;
|
|
@@ -54,7 +58,7 @@ export type IManifestNode = {
|
|
|
54
58
|
resolveInitialProps: (c: IArrmatron) => Hash;
|
|
55
59
|
|
|
56
60
|
createArrmatron: (platform: IPlatform, parent?: Arrmatron, scope?: Arrmatron) => Arrmatron;
|
|
57
|
-
}
|
|
61
|
+
};
|
|
58
62
|
|
|
59
63
|
export type IPlatform = {
|
|
60
64
|
log(arg0: LogEntry): void;
|
|
@@ -66,10 +70,14 @@ export type IPlatform = {
|
|
|
66
70
|
getResource(key: string | string[]): Data;
|
|
67
71
|
getFunction(id: string): (...args: unknown[]) => unknown;
|
|
68
72
|
redraw(source: IArrmatron, root: IArrmatron): void;
|
|
69
|
-
createComponent(
|
|
70
|
-
|
|
73
|
+
createComponent(
|
|
74
|
+
def: IComponentDescriptor | string,
|
|
75
|
+
initials: Hash,
|
|
76
|
+
ctx: IComponentContext
|
|
77
|
+
): IComponent | Hash;
|
|
78
|
+
};
|
|
71
79
|
|
|
72
|
-
export type ComponentConstructor = new (initials: Hash,
|
|
80
|
+
export type ComponentConstructor = new (initials: Hash, ctx: IComponentContext) => IComponent;
|
|
73
81
|
|
|
74
82
|
export type IComponentDescriptor = {
|
|
75
83
|
tag?: string;
|
|
@@ -77,6 +85,6 @@ export type IComponentDescriptor = {
|
|
|
77
85
|
Ctor?: ComponentConstructor;
|
|
78
86
|
unknown?: boolean;
|
|
79
87
|
native?: boolean;
|
|
80
|
-
}
|
|
88
|
+
};
|
|
81
89
|
|
|
82
|
-
export type EContent = Map<Uid, IManifestNode>;
|
|
90
|
+
export type EContent = Map<Uid, IManifestNode>;
|
package/docs/glossary.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Glossary
|
|
2
|
-
|
|
3
|
-
| _Term_ | _Description_ |
|
|
4
|
-
| ----------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5
|
-
| `Program` | A formal prescription given to a runtime to process (to apply trasformation rules in some order) incoming data. |
|
|
6
|
-
| `Type` | A specification that defines the possible values that can be reffered by a variable. |
|
|
7
|
-
| `Variable` | A program entity, which may reffer some data value. |
|
|
8
|
-
| `Object-oriented programming` | A programming style that evolves around composing components, which interact with each others by handling streams of events. |
|
|
9
|
-
| `Component` | An individual runtime entity that has a lifecycle, state, and behavior. Components can interact with others and are treated as black boxes. |
|
|
10
|
-
| `Class` | A design-time specification for the state and behavior of a component. It provides guidance for the runtime to create and manage instances of the component. |
|
|
11
|
-
| `State` | A private data structure that is managed by a component. |
|
|
12
|
-
| `Property` | A public getter/setter pair that allows access to a state value by a specified key. |
|
|
13
|
-
| `Behavior` | A specification that defines how a component should change its state in response to external impacts. |
|
|
14
|
-
| `Interface` | A design-time specification of the methods and signatures that can be used to interact with a component. |
|
|
15
|
-
| `Composition` | The | parent-child and context-content relationships between components. It defines the scope and lifecycle of the components that they can interact with. |
|
|
16
|
-
| `Template` | A text written in a formal language that allows for the description of component composition and property binding in a declarative way, focused on the desired outcome rather than the implementation details. |
|
|
17
|
-
| `Property binding` | A formal sentence that expresses a dependency between the value of a target component's property and other properties. It ensures that the target property is updated whenever the dependency changes. |
|
|
18
|
-
| `Property propagation` | A one-way binding that passes property values from a container component to its contents. |
|
|
19
|
-
| `Hook` | A component-bound method that is called by the runtime at specific points in the component's lifecycle. |
|