@retronew/call-vue 0.4.0 → 0.4.1
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/dist/index.mjs +1 -172
- package/dist/mutation-flow/index.mjs +1 -29
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,172 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/createCallable/store.ts
|
|
3
|
-
/**
|
|
4
|
-
* Vue-native replacement for react-call's `useSyncExternalStore`-backed
|
|
5
|
-
* store: `shallowRef` already gives us a single reactive snapshot per
|
|
6
|
-
* store, so there's no separate subscribe/getSnapshot machinery to hand-roll.
|
|
7
|
-
*
|
|
8
|
-
* `rootCount` plays the role react-call's `listeners.size` plays — it's how
|
|
9
|
-
* `assertSingleRoot` in `createCallable` detects a missing or duplicated
|
|
10
|
-
* `<Root>`. It's tracked here (rather than as its own `ref`) because it must
|
|
11
|
-
* never trigger a re-render on its own; only stack changes should.
|
|
12
|
-
*/
|
|
13
|
-
function createStackStore() {
|
|
14
|
-
let nextKey = 0;
|
|
15
|
-
let upsertPromise = null;
|
|
16
|
-
let rootCount = 0;
|
|
17
|
-
const stack = shallowRef([]);
|
|
18
|
-
return {
|
|
19
|
-
stack,
|
|
20
|
-
add: (call) => {
|
|
21
|
-
stack.value = [...stack.value, {
|
|
22
|
-
...call,
|
|
23
|
-
key: String(nextKey++)
|
|
24
|
-
}];
|
|
25
|
-
},
|
|
26
|
-
set: (promise, updateFn) => {
|
|
27
|
-
stack.value = stack.value.map((call) => promise && call.promise !== promise ? call : updateFn(call));
|
|
28
|
-
},
|
|
29
|
-
remove: (promises) => {
|
|
30
|
-
stack.value = stack.value.filter((c) => !promises.has(c.promise));
|
|
31
|
-
},
|
|
32
|
-
/** Call once from a mounted `<Root>`; call the returned function on unmount. */
|
|
33
|
-
mountRoot: () => {
|
|
34
|
-
rootCount++;
|
|
35
|
-
return () => {
|
|
36
|
-
rootCount--;
|
|
37
|
-
if (rootCount === 0) {
|
|
38
|
-
nextKey = 0;
|
|
39
|
-
stack.value = [];
|
|
40
|
-
upsertPromise = null;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
|
-
getRootCount: () => rootCount,
|
|
45
|
-
getUpsertPromise: () => upsertPromise,
|
|
46
|
-
setUpsertPromise: (p) => {
|
|
47
|
-
upsertPromise = p;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/createCallable/index.ts
|
|
53
|
-
/**
|
|
54
|
-
* Turns a Vue component into a "callable": mount it once as `<Confirm />`
|
|
55
|
-
* and then `await Confirm.call(props)` from anywhere to push an instance
|
|
56
|
-
* onto its stack and get back a promise that resolves when the instance
|
|
57
|
-
* calls `call.end(response)`.
|
|
58
|
-
*
|
|
59
|
-
* This is a Vue-native port of react-call's `createCallable` — see
|
|
60
|
-
* `skills/call-vue/SKILL.md` for the full mental model (stack, upsert,
|
|
61
|
-
* lifecycle constraints).
|
|
62
|
-
*/
|
|
63
|
-
function createCallable(UserComponent, unmountingDelay = 0) {
|
|
64
|
-
const store = createStackStore();
|
|
65
|
-
const createEnd = (promise) => (response) => {
|
|
66
|
-
const ending = /* @__PURE__ */ new Set();
|
|
67
|
-
store.set(promise, (call) => {
|
|
68
|
-
call.resolve(response);
|
|
69
|
-
ending.add(call.promise);
|
|
70
|
-
return {
|
|
71
|
-
...call,
|
|
72
|
-
ended: true
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
globalThis.setTimeout(() => store.remove(ending), unmountingDelay);
|
|
76
|
-
};
|
|
77
|
-
const assertSingleRoot = () => {
|
|
78
|
-
const count = store.getRootCount();
|
|
79
|
-
if (!count) throw new Error("No <Root> found!");
|
|
80
|
-
if (count > 1) throw new Error("Multiple instances of <Root> found!");
|
|
81
|
-
};
|
|
82
|
-
const call = (props) => {
|
|
83
|
-
assertSingleRoot();
|
|
84
|
-
let resolve;
|
|
85
|
-
const promise = new Promise((res) => {
|
|
86
|
-
resolve = res;
|
|
87
|
-
});
|
|
88
|
-
store.add({
|
|
89
|
-
props,
|
|
90
|
-
end: createEnd(promise),
|
|
91
|
-
ended: false,
|
|
92
|
-
promise,
|
|
93
|
-
resolve
|
|
94
|
-
});
|
|
95
|
-
return promise;
|
|
96
|
-
};
|
|
97
|
-
const upsert = (props) => {
|
|
98
|
-
assertSingleRoot();
|
|
99
|
-
const existing = store.getUpsertPromise();
|
|
100
|
-
if (existing) {
|
|
101
|
-
store.set(existing, (c) => ({
|
|
102
|
-
...c,
|
|
103
|
-
props
|
|
104
|
-
}));
|
|
105
|
-
return existing;
|
|
106
|
-
}
|
|
107
|
-
let resolve;
|
|
108
|
-
const promise = new Promise((res) => {
|
|
109
|
-
resolve = res;
|
|
110
|
-
});
|
|
111
|
-
store.setUpsertPromise(promise);
|
|
112
|
-
store.add({
|
|
113
|
-
props,
|
|
114
|
-
end: (response) => {
|
|
115
|
-
store.setUpsertPromise(null);
|
|
116
|
-
createEnd(promise)(response);
|
|
117
|
-
},
|
|
118
|
-
ended: false,
|
|
119
|
-
promise,
|
|
120
|
-
resolve
|
|
121
|
-
});
|
|
122
|
-
return promise;
|
|
123
|
-
};
|
|
124
|
-
const end = ((...args) => {
|
|
125
|
-
const targeted = args.length === 2;
|
|
126
|
-
const promise = targeted ? args[0] : null;
|
|
127
|
-
const response = targeted ? args[1] : args[0];
|
|
128
|
-
if (!targeted || promise === store.getUpsertPromise()) store.setUpsertPromise(null);
|
|
129
|
-
return createEnd(promise)(response);
|
|
130
|
-
});
|
|
131
|
-
const update = (...args) => {
|
|
132
|
-
const targeted = args.length === 2;
|
|
133
|
-
store.set(targeted ? args[0] : null, (c) => ({
|
|
134
|
-
...c,
|
|
135
|
-
props: {
|
|
136
|
-
...c.props,
|
|
137
|
-
...targeted ? args[1] : args[0]
|
|
138
|
-
}
|
|
139
|
-
}));
|
|
140
|
-
};
|
|
141
|
-
const Root = defineComponent({
|
|
142
|
-
name: "CallableRoot",
|
|
143
|
-
inheritAttrs: false,
|
|
144
|
-
setup(_, { attrs }) {
|
|
145
|
-
let unmountRoot;
|
|
146
|
-
onMounted(() => {
|
|
147
|
-
unmountRoot = store.mountRoot();
|
|
148
|
-
});
|
|
149
|
-
onUnmounted(() => unmountRoot?.());
|
|
150
|
-
return () => store.stack.value.map((item, index, stack) => h(UserComponent, {
|
|
151
|
-
...item.props,
|
|
152
|
-
key: item.key,
|
|
153
|
-
call: {
|
|
154
|
-
key: item.key,
|
|
155
|
-
end: item.end,
|
|
156
|
-
ended: item.ended,
|
|
157
|
-
root: attrs,
|
|
158
|
-
index,
|
|
159
|
-
stackSize: stack.length
|
|
160
|
-
}
|
|
161
|
-
}));
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
return Object.assign(Root, {
|
|
165
|
-
call,
|
|
166
|
-
upsert,
|
|
167
|
-
end,
|
|
168
|
-
update
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
//#endregion
|
|
172
|
-
export { createCallable };
|
|
1
|
+
import{defineComponent as e,h as t,onMounted as n,onUnmounted as r,shallowRef as i}from"vue";function a(){let e=0,t=null,n=0,r=i([]);return{stack:r,add:t=>{r.value=[...r.value,{...t,key:String(e++)}]},set:(e,t)=>{r.value=r.value.map(n=>e&&n.promise!==e?n:t(n))},remove:e=>{r.value=r.value.filter(t=>!e.has(t.promise))},mountRoot:()=>(n++,()=>{n--,n===0&&(e=0,r.value=[],t=null)}),getRootCount:()=>n,getUpsertPromise:()=>t,setUpsertPromise:e=>{t=e}}}function o(i,o=0){let s=a(),c=e=>t=>{let n=new Set;s.set(e,e=>(e.resolve(t),n.add(e.promise),{...e,ended:!0})),globalThis.setTimeout(()=>s.remove(n),o)},l=()=>{let e=s.getRootCount();if(!e)throw Error(`No <Root> found!`);if(e>1)throw Error(`Multiple instances of <Root> found!`)},u=e=>{l();let t,n=new Promise(e=>{t=e});return s.add({props:e,end:c(n),ended:!1,promise:n,resolve:t}),n},d=e=>{l();let t=s.getUpsertPromise();if(t)return s.set(t,t=>({...t,props:e})),t;let n,r=new Promise(e=>{n=e});return s.setUpsertPromise(r),s.add({props:e,end:e=>{s.setUpsertPromise(null),c(r)(e)},ended:!1,promise:r,resolve:n}),r},f=((...e)=>{let t=e.length===2,n=t?e[0]:null,r=t?e[1]:e[0];return(!t||n===s.getUpsertPromise())&&s.setUpsertPromise(null),c(n)(r)}),p=(...e)=>{let t=e.length===2;s.set(t?e[0]:null,n=>({...n,props:{...n.props,...t?e[1]:e[0]}}))},m=e({name:`CallableRoot`,inheritAttrs:!1,setup(e,{attrs:a}){let o;return n(()=>{o=s.mountRoot()}),r(()=>o?.()),()=>s.stack.value.map((e,n,r)=>t(i,{...e.props,key:e.key,call:{key:e.key,end:e.end,ended:e.ended,root:a,index:n,stackSize:r.length}}))}});return Object.assign(m,{call:u,upsert:d,end:f,update:p})}export{o as createCallable};
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/mutation-flow/index.ts
|
|
3
|
-
const noopChain = { orEnd: () => {} };
|
|
4
|
-
function resolveMutation(source) {
|
|
5
|
-
return isRef(source) ? source.value : source;
|
|
6
|
-
}
|
|
7
|
-
function useMutationFlow(call, mutationSource) {
|
|
8
|
-
const pending = ref(false);
|
|
9
|
-
let inFlight = false;
|
|
10
|
-
const trigger = ((payload) => {
|
|
11
|
-
if (inFlight) return noopChain;
|
|
12
|
-
const mutationFn = resolveMutation(mutationSource);
|
|
13
|
-
if (!mutationFn) return { orEnd: (value) => call.end(value) };
|
|
14
|
-
inFlight = true;
|
|
15
|
-
pending.value = true;
|
|
16
|
-
mutationFn(call, payload).finally(() => {
|
|
17
|
-
inFlight = false;
|
|
18
|
-
pending.value = false;
|
|
19
|
-
});
|
|
20
|
-
return noopChain;
|
|
21
|
-
});
|
|
22
|
-
Object.defineProperty(trigger, "pending", {
|
|
23
|
-
enumerable: true,
|
|
24
|
-
get: () => pending.value
|
|
25
|
-
});
|
|
26
|
-
return trigger;
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
export { useMutationFlow };
|
|
1
|
+
import{isRef as e,ref as t}from"vue";const n={orEnd:()=>{}};function r(t){return e(t)?t.value:t}function i(e,i){let a=t(!1),o=!1,s=(t=>{if(o)return n;let s=r(i);return s?(o=!0,a.value=!0,s(e,t).finally(()=>{o=!1,a.value=!1}),n):{orEnd:t=>e.end(t)}});return Object.defineProperty(s,"pending",{enumerable:!0,get:()=>a.value}),s}export{i as useMutationFlow};
|