@lunora/vue 1.0.0-alpha.32 → 1.0.0-alpha.34
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 +4 -4
- package/dist/packem_shared/stable-key-wv6eP48B.mjs +40 -0
- package/dist/packem_shared/{useFlag-Diuk30zd.mjs → useFlag-Cc8rtueb.mjs} +1 -39
- package/dist/packem_shared/{useInfiniteQuery-DZMfVEuL.mjs → useInfiniteQuery-ZnvrirrF.mjs} +13 -3
- package/dist/packem_shared/usePresence-Bg671Pt0.mjs +75 -0
- package/dist/packem_shared/{useSubscription-DHoCEXNh.mjs → useSubscription-B2s-lc5e.mjs} +2 -1
- package/package.json +5 -5
- package/dist/packem_shared/usePresence-VXq5yGzE.mjs +0 -59
package/dist/index.mjs
CHANGED
|
@@ -3,11 +3,11 @@ export { hydratePreloaded } from './packem_shared/hydratePreloaded-Cnd2GmG1.mjs'
|
|
|
3
3
|
export { LUNORA_INJECTION_KEY, createLunora, provideLunora, useLunora } from './packem_shared/LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
4
4
|
export { useAuth } from './packem_shared/useAuth-yXaaF-ro.mjs';
|
|
5
5
|
export { default as useConnectionStatus } from './packem_shared/useConnectionStatus-DM_TOaW5.mjs';
|
|
6
|
-
export { useFlag, useFlags } from './packem_shared/useFlag-
|
|
6
|
+
export { useFlag, useFlags } from './packem_shared/useFlag-Cc8rtueb.mjs';
|
|
7
7
|
export { useMutation } from './packem_shared/useMutation-BKIwT8Jk.mjs';
|
|
8
8
|
export { useMutator } from './packem_shared/useMutator-B_Ahs_ZN.mjs';
|
|
9
|
-
export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-
|
|
10
|
-
export { usePresence } from './packem_shared/usePresence-
|
|
9
|
+
export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-ZnvrirrF.mjs';
|
|
10
|
+
export { usePresence } from './packem_shared/usePresence-Bg671Pt0.mjs';
|
|
11
11
|
export { subscribeToQuery, useQuery } from './packem_shared/subscribeToQuery-hVYyvgOD.mjs';
|
|
12
12
|
export { useRateLimit } from './packem_shared/useRateLimit-DlPWjcX1.mjs';
|
|
13
|
-
export { useSubscription } from './packem_shared/useSubscription-
|
|
13
|
+
export { useSubscription } from './packem_shared/useSubscription-B2s-lc5e.mjs';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const compareKeys = (a, b) => {
|
|
2
|
+
if (a < b) {
|
|
3
|
+
return -1;
|
|
4
|
+
}
|
|
5
|
+
return a > b ? 1 : 0;
|
|
6
|
+
};
|
|
7
|
+
const stableStringify = (value) => {
|
|
8
|
+
if (value === void 0) {
|
|
9
|
+
return "null";
|
|
10
|
+
}
|
|
11
|
+
if (typeof value === "bigint") {
|
|
12
|
+
throw new TypeError("stableStringify: cannot use a bigint in a cache key (query/subscription/shape args) — pass it as a string");
|
|
13
|
+
}
|
|
14
|
+
if (value === null || typeof value !== "object") {
|
|
15
|
+
return JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
19
|
+
}
|
|
20
|
+
const proto = Object.getPrototypeOf(value);
|
|
21
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
22
|
+
const name = value.constructor?.name ?? "value";
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
`stableStringify: cannot use a ${name} in a cache key (query/subscription/shape args) — only plain objects, arrays, and JSON primitives are supported`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const record = value;
|
|
28
|
+
const keys = Object.keys(record).toSorted(compareKeys);
|
|
29
|
+
const parts = [];
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const raw = record[key];
|
|
32
|
+
if (raw === void 0) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
36
|
+
}
|
|
37
|
+
return `{${parts.join(",")}}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { stableStringify as s };
|
|
@@ -1,45 +1,7 @@
|
|
|
1
1
|
import { shallowRef, watch, toValue } from 'vue';
|
|
2
|
+
import { s as stableStringify } from './stable-key-wv6eP48B.mjs';
|
|
2
3
|
import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
3
4
|
|
|
4
|
-
const compareKeys = (a, b) => {
|
|
5
|
-
if (a < b) {
|
|
6
|
-
return -1;
|
|
7
|
-
}
|
|
8
|
-
return a > b ? 1 : 0;
|
|
9
|
-
};
|
|
10
|
-
const stableStringify = (value) => {
|
|
11
|
-
if (value === void 0) {
|
|
12
|
-
return "null";
|
|
13
|
-
}
|
|
14
|
-
if (typeof value === "bigint") {
|
|
15
|
-
throw new TypeError("stableStringify: cannot use a bigint in a cache key (query/subscription/shape args) — pass it as a string");
|
|
16
|
-
}
|
|
17
|
-
if (value === null || typeof value !== "object") {
|
|
18
|
-
return JSON.stringify(value);
|
|
19
|
-
}
|
|
20
|
-
if (Array.isArray(value)) {
|
|
21
|
-
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
22
|
-
}
|
|
23
|
-
const proto = Object.getPrototypeOf(value);
|
|
24
|
-
if (proto !== null && proto !== Object.prototype) {
|
|
25
|
-
const name = value.constructor?.name ?? "value";
|
|
26
|
-
throw new TypeError(
|
|
27
|
-
`stableStringify: cannot use a ${name} in a cache key (query/subscription/shape args) — only plain objects, arrays, and JSON primitives are supported`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
const record = value;
|
|
31
|
-
const keys = Object.keys(record).toSorted(compareKeys);
|
|
32
|
-
const parts = [];
|
|
33
|
-
for (const key of keys) {
|
|
34
|
-
const raw = record[key];
|
|
35
|
-
if (raw === void 0) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
|
|
39
|
-
}
|
|
40
|
-
return `{${parts.join(",")}}`;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
5
|
const FLAGS_EVAL_PATH = "__lunora_flags__:eval";
|
|
44
6
|
const flagKind = (value) => {
|
|
45
7
|
const kind = typeof value;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { shallowRef, watch, toValue, onScopeDispose, computed } from 'vue';
|
|
2
2
|
import { initialPages, derivePaginationStatus, rebalance, applyLoadMore } from '@lunora/client/pagination';
|
|
3
|
+
import { s as stableStringify } from './stable-key-wv6eP48B.mjs';
|
|
3
4
|
import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
4
5
|
|
|
5
|
-
const buildPageKey = (functionPath, pageArgs) => `${functionPath}::${
|
|
6
|
+
const buildPageKey = (functionPath, pageArgs) => `${functionPath}::${stableStringify(pageArgs)}`;
|
|
6
7
|
const buildPageArgs = (page, baseArgs) => {
|
|
7
8
|
return {
|
|
8
9
|
...baseArgs,
|
|
@@ -14,6 +15,7 @@ const usePaginatedCore = (function_, args, options) => {
|
|
|
14
15
|
const { initialNumItems, shardKey } = options;
|
|
15
16
|
const pages = shallowRef(initialPages(initialNumItems));
|
|
16
17
|
const pageResults = shallowRef([]);
|
|
18
|
+
let lastLoadMoreCursor;
|
|
17
19
|
const activeSubs = /* @__PURE__ */ new Map();
|
|
18
20
|
const resultsByKey = /* @__PURE__ */ new Map();
|
|
19
21
|
const pendingPageKeys = /* @__PURE__ */ new Set();
|
|
@@ -49,6 +51,7 @@ const usePaginatedCore = (function_, args, options) => {
|
|
|
49
51
|
entry.unsub();
|
|
50
52
|
activeSubs.delete(originalKey);
|
|
51
53
|
resultsByKey.delete(entry.currentKey);
|
|
54
|
+
pendingPageKeys.delete(entry.currentKey);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
const coveredKeys = new Set([...activeSubs.values()].map((subEntry) => subEntry.currentKey));
|
|
@@ -93,13 +96,16 @@ const usePaginatedCore = (function_, args, options) => {
|
|
|
93
96
|
}
|
|
94
97
|
activeSubs.clear();
|
|
95
98
|
resultsByKey.clear();
|
|
99
|
+
pendingPageKeys.clear();
|
|
96
100
|
};
|
|
97
101
|
watch(
|
|
98
|
-
() => toValue(args),
|
|
99
|
-
(
|
|
102
|
+
() => stableStringify(toValue(args)),
|
|
103
|
+
() => {
|
|
104
|
+
const current = toValue(args);
|
|
100
105
|
teardownAll();
|
|
101
106
|
pages.value = initialPages(initialNumItems);
|
|
102
107
|
pageResults.value = [];
|
|
108
|
+
lastLoadMoreCursor = void 0;
|
|
103
109
|
if (current !== "skip") {
|
|
104
110
|
syncSubscriptions(pages.value, current);
|
|
105
111
|
rebuildPageResults(pages.value, current);
|
|
@@ -135,10 +141,14 @@ const usePaginatedCore = (function_, args, options) => {
|
|
|
135
141
|
if (currentStatus !== "CanLoadMore") {
|
|
136
142
|
return;
|
|
137
143
|
}
|
|
144
|
+
if (nextCursor === lastLoadMoreCursor) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
138
147
|
const next = applyLoadMore(pages.value, nextCursor, numberItems);
|
|
139
148
|
if (!next) {
|
|
140
149
|
return;
|
|
141
150
|
}
|
|
151
|
+
lastLoadMoreCursor = nextCursor;
|
|
142
152
|
const oldTail = pages.value.at(-1);
|
|
143
153
|
const newPinnedPage = next.at(-2);
|
|
144
154
|
if (oldTail && newPinnedPage) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { shallowRef, getCurrentScope, onScopeDispose } from 'vue';
|
|
2
|
+
import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
3
|
+
|
|
4
|
+
const randomSessionId = (prefix = "sess") => {
|
|
5
|
+
if (typeof crypto !== "undefined") {
|
|
6
|
+
if (typeof crypto.randomUUID === "function") {
|
|
7
|
+
return crypto.randomUUID();
|
|
8
|
+
}
|
|
9
|
+
if (typeof crypto.getRandomValues === "function") {
|
|
10
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
11
|
+
return `${prefix}-${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return `${prefix}-${Date.now().toString(36)}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DEFAULT_INTERVAL_MS = 1e4;
|
|
18
|
+
const usePresence = (roomId, options) => {
|
|
19
|
+
const client = useLunora();
|
|
20
|
+
const { heartbeat, intervalMs = DEFAULT_INTERVAL_MS, listPresent, shardKey } = options;
|
|
21
|
+
const sessionId = options.sessionId ?? randomSessionId();
|
|
22
|
+
const present = shallowRef(void 0);
|
|
23
|
+
let latestData = options.data;
|
|
24
|
+
const sendHeartbeat = () => {
|
|
25
|
+
const args = {
|
|
26
|
+
roomId,
|
|
27
|
+
sessionId,
|
|
28
|
+
...latestData === void 0 ? {} : { data: latestData }
|
|
29
|
+
};
|
|
30
|
+
client.mutation(heartbeat, args, { shardKey }).catch(() => void 0);
|
|
31
|
+
};
|
|
32
|
+
const setData = (next) => {
|
|
33
|
+
latestData = next;
|
|
34
|
+
sendHeartbeat();
|
|
35
|
+
};
|
|
36
|
+
if (globalThis.window !== void 0) {
|
|
37
|
+
sendHeartbeat();
|
|
38
|
+
const intervalHandle = setInterval(sendHeartbeat, intervalMs);
|
|
39
|
+
const onVisible = () => {
|
|
40
|
+
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
41
|
+
sendHeartbeat();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
if (typeof document !== "undefined") {
|
|
45
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
46
|
+
}
|
|
47
|
+
const releaseConnectionContext = client.acquireConnectionContext({ roomId, sessionId }, { shardKey });
|
|
48
|
+
const unsubscribe = client.subscribe(
|
|
49
|
+
listPresent,
|
|
50
|
+
{ roomId },
|
|
51
|
+
(value) => {
|
|
52
|
+
present.value = value;
|
|
53
|
+
},
|
|
54
|
+
{ shardKey }
|
|
55
|
+
);
|
|
56
|
+
const teardown = () => {
|
|
57
|
+
clearInterval(intervalHandle);
|
|
58
|
+
if (typeof document !== "undefined") {
|
|
59
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
60
|
+
}
|
|
61
|
+
releaseConnectionContext();
|
|
62
|
+
unsubscribe();
|
|
63
|
+
};
|
|
64
|
+
if (getCurrentScope()) {
|
|
65
|
+
onScopeDispose(teardown);
|
|
66
|
+
} else {
|
|
67
|
+
console.warn(
|
|
68
|
+
"[@lunora/vue] usePresence called with no active effect scope — its heartbeat interval and live subscription will not be cleaned up automatically. Call it inside setup()/an effect scope."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { present, sessionId, setData };
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export { usePresence };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createQuerySubscription } from '@lunora/client/query';
|
|
2
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
3
|
import { ref, watch, toValue, onScopeDispose } from 'vue';
|
|
3
4
|
import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
4
5
|
|
|
@@ -24,7 +25,7 @@ const useSubscription = (function_, args, options = {}) => {
|
|
|
24
25
|
error.value = void 0;
|
|
25
26
|
},
|
|
26
27
|
onError: (subscriptionError) => {
|
|
27
|
-
error.value = new Error(subscriptionError.message);
|
|
28
|
+
error.value = subscriptionError.code === void 0 ? new Error(subscriptionError.message) : new LunoraError(subscriptionError.code, subscriptionError.message);
|
|
28
29
|
data.value = void 0;
|
|
29
30
|
},
|
|
30
31
|
onReset: () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/vue",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.34",
|
|
4
4
|
"description": "Vue adapter for Lunora — live composables, optimistic mutations, and reactive loaders",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@lunora/client": "1.0.0-alpha.
|
|
58
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
59
|
-
"@lunora/ratelimit": "1.0.0-alpha.
|
|
60
|
-
"@lunora/runtime": "1.0.0-alpha.
|
|
57
|
+
"@lunora/client": "1.0.0-alpha.21",
|
|
58
|
+
"@lunora/errors": "1.0.0-alpha.4",
|
|
59
|
+
"@lunora/ratelimit": "1.0.0-alpha.7",
|
|
60
|
+
"@lunora/runtime": "1.0.0-alpha.24"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"vue": "^3.5.0"
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { shallowRef, onScopeDispose } from 'vue';
|
|
2
|
-
import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
|
|
3
|
-
|
|
4
|
-
const makeSessionId = () => {
|
|
5
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
6
|
-
return crypto.randomUUID();
|
|
7
|
-
}
|
|
8
|
-
return `sess-${Math.random().toString(36).slice(2)}-${String(Date.now())}`;
|
|
9
|
-
};
|
|
10
|
-
const DEFAULT_INTERVAL_MS = 1e4;
|
|
11
|
-
const usePresence = (roomId, options) => {
|
|
12
|
-
const client = useLunora();
|
|
13
|
-
const { heartbeat, intervalMs = DEFAULT_INTERVAL_MS, listPresent, shardKey } = options;
|
|
14
|
-
const sessionId = options.sessionId ?? makeSessionId();
|
|
15
|
-
const present = shallowRef(void 0);
|
|
16
|
-
let latestData = options.data;
|
|
17
|
-
const sendHeartbeat = () => {
|
|
18
|
-
const args = {
|
|
19
|
-
roomId,
|
|
20
|
-
sessionId,
|
|
21
|
-
...latestData === void 0 ? {} : { data: latestData }
|
|
22
|
-
};
|
|
23
|
-
client.mutation(heartbeat, args, { shardKey }).catch(() => void 0);
|
|
24
|
-
};
|
|
25
|
-
const setData = (next) => {
|
|
26
|
-
latestData = next;
|
|
27
|
-
sendHeartbeat();
|
|
28
|
-
};
|
|
29
|
-
sendHeartbeat();
|
|
30
|
-
const intervalHandle = setInterval(sendHeartbeat, intervalMs);
|
|
31
|
-
const onVisible = () => {
|
|
32
|
-
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
33
|
-
sendHeartbeat();
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
if (typeof document !== "undefined") {
|
|
37
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
38
|
-
}
|
|
39
|
-
const releaseConnectionContext = client.acquireConnectionContext({ roomId, sessionId }, { shardKey });
|
|
40
|
-
const unsubscribe = client.subscribe(
|
|
41
|
-
listPresent,
|
|
42
|
-
{ roomId },
|
|
43
|
-
(value) => {
|
|
44
|
-
present.value = value;
|
|
45
|
-
},
|
|
46
|
-
{ shardKey }
|
|
47
|
-
);
|
|
48
|
-
onScopeDispose(() => {
|
|
49
|
-
clearInterval(intervalHandle);
|
|
50
|
-
if (typeof document !== "undefined") {
|
|
51
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
52
|
-
}
|
|
53
|
-
releaseConnectionContext();
|
|
54
|
-
unsubscribe();
|
|
55
|
-
});
|
|
56
|
-
return { present, sessionId, setData };
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export { usePresence };
|