MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1const knownServerReferences = new WeakMap();
2const boundCache = new WeakMap();
3const FunctionBind = Function.prototype.bind;
4const ArraySlice = Array.prototype.slice;
5
6function encodeFormData(reference) {
7 let resolve;
8 let reject;
9 const thenable = new Promise((res, rej) => {
10 resolve = res;
11 reject = rej;
12 });
13
14 Promise.resolve(reference.bound).then((bound) => {
15 const data = new FormData();
16 data.append('id', reference.id);
17 data.append('boundCount', String(bound.length));
18 thenable.status = 'fulfilled';
19 thenable.value = data;
20 resolve(data);
21 }, (error) => {
22 thenable.status = 'rejected';
23 thenable.reason = error;
24 reject(error);
25 });
26
27 return thenable;
28}
29
30function defaultEncodeFormAction(identifierPrefix) {
31 let referenceClosure = knownServerReferences.get(this);
32 let data = null;
33
34 if (referenceClosure.bound !== null) {
35 data = boundCache.get(referenceClosure);
36 if (!data) {
37 data = encodeFormData({
38 id: referenceClosure.id,
39 bound: referenceClosure.bound,
40 });
41 boundCache.set(referenceClosure, data);
42 }
43
44 if (data.status === 'rejected') throw data.reason;
45 if (data.status !== 'fulfilled') throw data;
46
47 referenceClosure = data.value;
48 const prefixedData = new FormData();
49 referenceClosure.forEach((value, key) => {
50 prefixedData.append(`$ACTION_${identifierPrefix}:${key}`, value);
51 });
52 data = prefixedData;
53 referenceClosure = `$ACTION_REF_${identifierPrefix}`;
54 } else {
55 referenceClosure = `$ACTION_ID_${referenceClosure.id}`;
56 }
57
58 return {
59 name: referenceClosure,
60 method: 'POST',
61 encType: 'multipart/form-data',
62 data,
63 };
64}
65
66function bind() {
67 const referenceClosure = knownServerReferences.get(this);
68 if (!referenceClosure) return FunctionBind.apply(this, arguments);
69
70 const newFn = referenceClosure.originalBind.apply(this, arguments);
71 const args = ArraySlice.call(arguments, 1);
72 const boundPromise = referenceClosure.bound !== null
73 ? Promise.resolve(referenceClosure.bound).then((boundArgs) => boundArgs.concat(args))
74 : Promise.resolve(args);
75
76 knownServerReferences.set(newFn, {
77 id: referenceClosure.id,
78 originalBind: newFn.bind,
79 bound: boundPromise,
80 });
81
82 Object.defineProperties(newFn, {
83 $$FORM_ACTION: { value: this.$$FORM_ACTION },
84 bind: { value: bind },
85 });
86
87 return newFn;
88}
89
90function registerBoundServerReference(reference, id, bound) {
91 knownServerReferences.set(reference, {
92 id,
93 originalBind: reference.bind,
94 bound,
95 });
96
97 Object.defineProperties(reference, {
98 $$FORM_ACTION: { value: defaultEncodeFormAction },
99 bind: { value: bind },
100 });
101}
102
103async function action(x) {
104 return x;
105}
106
107registerBoundServerReference(action, 'demo', null);
108
109const bound = action.bind(null, 1);
110console.log(`bound.formAction.type:${typeof bound.$$FORM_ACTION}`);
111
112try {
113 const info = bound.$$FORM_ACTION('demo');
114 console.log(`first.name:${info.name}`);
115} catch (error) {
116 console.log(`first.throw.type:${typeof error}`);
117 console.log(`first.throw.then:${typeof error?.then}`);
118 console.log(`first.throw.status:${error?.status ?? 'missing'}`);
119}
120
121await Promise.resolve();
122
123try {
124 const info = bound.$$FORM_ACTION('demo');
125 console.log(`second.name:${info.name}`);
126 console.log(`second.method:${info.method}`);
127 console.log(`second.encType:${info.encType}`);
128 const pairs = [];
129 info.data?.forEach((value, key) => {
130 pairs.push(`${key}=${value}`);
131 });
132 console.log(`second.data:${pairs.join(',')}`);
133} catch (error) {
134 console.log(`second.throw:${error?.message ?? String(error)}`);
135}