@lunora/vue 1.0.0-alpha.4 → 1.0.0-alpha.40

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.
Files changed (27) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +2 -0
  3. package/__assets__/package-og.svg +1 -1
  4. package/dist/index.d.mts +825 -188
  5. package/dist/index.d.ts +825 -188
  6. package/dist/index.mjs +18 -10
  7. package/dist/packem_shared/{AuthLoading-_6-uOycE.mjs → AuthLoading-DKra8yfG.mjs} +1 -1
  8. package/dist/packem_shared/{LUNORA_INJECTION_KEY-DtFXLDQ_.mjs → LUNORA_INJECTION_KEY-DQM5MgsC.mjs} +5 -1
  9. package/dist/packem_shared/{hydratePreloaded-rVY68iFv.mjs → hydratePreloaded-Cnd2GmG1.mjs} +2 -2
  10. package/dist/packem_shared/stable-key-C8JHuJL7.mjs +40 -0
  11. package/dist/packem_shared/{subscribeToQuery-Cv5YL-SI.mjs → subscribeToQuery-hVYyvgOD.mjs} +1 -1
  12. package/dist/packem_shared/useAgent-TEp9OWs3.mjs +28 -0
  13. package/dist/packem_shared/useAgentChat-DQmekE-E.mjs +108 -0
  14. package/dist/packem_shared/useAgentState-CeDpLTib.mjs +12 -0
  15. package/dist/packem_shared/useAgentToolEvents-DfCy9INR.mjs +62 -0
  16. package/dist/packem_shared/{useAuth-C2aEzXXH.mjs → useAuth-yXaaF-ro.mjs} +1 -1
  17. package/dist/packem_shared/{useConnectionStatus-CDgduQYe.mjs → useConnectionStatus-DM_TOaW5.mjs} +1 -1
  18. package/dist/packem_shared/useFlag-B8Gq7oUu.mjs +70 -0
  19. package/dist/packem_shared/{useInfiniteQuery-CEfbw7Nw.mjs → useInfiniteQuery-CMAUPQyd.mjs} +114 -4
  20. package/dist/packem_shared/{useMutation-DWubV5pv.mjs → useMutation-BKIwT8Jk.mjs} +1 -1
  21. package/dist/packem_shared/useMutator-B_Ahs_ZN.mjs +19 -0
  22. package/dist/packem_shared/usePresence-Bg671Pt0.mjs +75 -0
  23. package/dist/packem_shared/useStream-DK2TNPo4.mjs +65 -0
  24. package/dist/packem_shared/{useSubscription-Ol0AZFUv.mjs → useSubscription-B2s-lc5e.mjs} +3 -2
  25. package/dist/packem_shared/useVoiceAgent-DuNU5TE_.mjs +388 -0
  26. package/package.json +6 -5
  27. package/dist/packem_shared/usePresence-BeN5xaZM.mjs +0 -59
package/dist/index.mjs CHANGED
@@ -1,11 +1,19 @@
1
- export { AuthLoading, Authenticated, Unauthenticated } from './packem_shared/AuthLoading-_6-uOycE.mjs';
2
- export { hydratePreloaded } from './packem_shared/hydratePreloaded-rVY68iFv.mjs';
3
- export { LUNORA_INJECTION_KEY, createLunora, provideLunora, useLunora } from './packem_shared/LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
4
- export { useAuth } from './packem_shared/useAuth-C2aEzXXH.mjs';
5
- export { default as useConnectionStatus } from './packem_shared/useConnectionStatus-CDgduQYe.mjs';
6
- export { useMutation } from './packem_shared/useMutation-DWubV5pv.mjs';
7
- export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-CEfbw7Nw.mjs';
8
- export { usePresence } from './packem_shared/usePresence-BeN5xaZM.mjs';
9
- export { subscribeToQuery, useQuery } from './packem_shared/subscribeToQuery-Cv5YL-SI.mjs';
1
+ export { AuthLoading, Authenticated, Unauthenticated } from './packem_shared/AuthLoading-DKra8yfG.mjs';
2
+ export { hydratePreloaded } from './packem_shared/hydratePreloaded-Cnd2GmG1.mjs';
3
+ export { LUNORA_INJECTION_KEY, createLunora, provideLunora, useLunora } from './packem_shared/LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
+ export { useAgent } from './packem_shared/useAgent-TEp9OWs3.mjs';
5
+ export { useAgentChat } from './packem_shared/useAgentChat-DQmekE-E.mjs';
6
+ export { useAgentState } from './packem_shared/useAgentState-CeDpLTib.mjs';
7
+ export { useAgentToolEvents } from './packem_shared/useAgentToolEvents-DfCy9INR.mjs';
8
+ export { useAuth } from './packem_shared/useAuth-yXaaF-ro.mjs';
9
+ export { default as useConnectionStatus } from './packem_shared/useConnectionStatus-DM_TOaW5.mjs';
10
+ export { useFlag, useFlags } from './packem_shared/useFlag-B8Gq7oUu.mjs';
11
+ export { useMutation } from './packem_shared/useMutation-BKIwT8Jk.mjs';
12
+ export { useMutator } from './packem_shared/useMutator-B_Ahs_ZN.mjs';
13
+ export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-CMAUPQyd.mjs';
14
+ export { usePresence } from './packem_shared/usePresence-Bg671Pt0.mjs';
15
+ export { subscribeToQuery, useQuery } from './packem_shared/subscribeToQuery-hVYyvgOD.mjs';
10
16
  export { useRateLimit } from './packem_shared/useRateLimit-DlPWjcX1.mjs';
11
- export { useSubscription } from './packem_shared/useSubscription-Ol0AZFUv.mjs';
17
+ export { useStream } from './packem_shared/useStream-DK2TNPo4.mjs';
18
+ export { useSubscription } from './packem_shared/useSubscription-B2s-lc5e.mjs';
19
+ export { useVoiceAgent } from './packem_shared/useVoiceAgent-DuNU5TE_.mjs';
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, computed } from 'vue';
2
- import { useAuth } from './useAuth-C2aEzXXH.mjs';
2
+ import { useAuth } from './useAuth-yXaaF-ro.mjs';
3
3
 
4
4
  const Authenticated = defineComponent({
5
5
  name: "Authenticated",
@@ -1,3 +1,4 @@
1
+ import { LunoraError } from '@lunora/errors';
1
2
  import { inject, provide } from 'vue';
2
3
 
3
4
  const LUNORA_INJECTION_KEY = /* @__PURE__ */ Symbol("lunora.client");
@@ -14,7 +15,10 @@ const provideLunora = (client) => {
14
15
  const useLunora = () => {
15
16
  const client = inject(LUNORA_INJECTION_KEY, void 0);
16
17
  if (!client) {
17
- throw new Error("useLunora(): no LunoraClient provided — call app.use(createLunora(client)) or provideLunora(client) in a parent setup().");
18
+ throw new LunoraError(
19
+ "INTERNAL",
20
+ "useLunora(): no LunoraClient provided — call app.use(createLunora(client)) or provideLunora(client) in a parent setup()."
21
+ );
18
22
  }
19
23
  return client;
20
24
  };
@@ -1,5 +1,5 @@
1
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
2
- import { subscribeToQuery } from './subscribeToQuery-Cv5YL-SI.mjs';
1
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
2
+ import { subscribeToQuery } from './subscribeToQuery-hVYyvgOD.mjs';
3
3
 
4
4
  const hydratePreloaded = (preloaded) => {
5
5
  const client = useLunora();
@@ -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 stable JSON cache key — pass it as a string, or use stableWireKey");
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 stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`
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,6 +1,6 @@
1
1
  import { createQuerySubscription } from '@lunora/client/query';
2
2
  import { shallowRef, getCurrentScope, onScopeDispose, watch, toValue } from 'vue';
3
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
3
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
4
 
5
5
  const subscribeToQuery = (client, function_, args, options = {}) => {
6
6
  const data = shallowRef(options.seed);
@@ -0,0 +1,28 @@
1
+ import { computed, toValue } from 'vue';
2
+ import { useMutation } from './useMutation-BKIwT8Jk.mjs';
3
+ import { useSubscription } from './useSubscription-B2s-lc5e.mjs';
4
+
5
+ const NO_MUTATION_REF = { __lunoraRef: "" };
6
+ const useAgent = (options) => {
7
+ const { api, cancel: cancelReference, run: runReference, runArgs, threadKey } = options;
8
+ const runMutation = useMutation(runReference);
9
+ const cancelMutation = useMutation(cancelReference ?? NO_MUTATION_REF);
10
+ const { data: threadData } = useSubscription(api.agents.agentThread, () => {
11
+ return { key: toValue(threadKey) };
12
+ });
13
+ const thread = computed(() => threadData.value);
14
+ const status = computed(() => thread.value?.status);
15
+ const run = async (input, arguments_) => {
16
+ await runMutation.mutate({ input, threadKey: toValue(threadKey), ...runArgs, ...arguments_ });
17
+ };
18
+ const cancel = async () => {
19
+ const instanceId = thread.value?.instanceId;
20
+ if (cancelReference === void 0 || instanceId === void 0) {
21
+ return;
22
+ }
23
+ await cancelMutation.mutate({ instanceId, threadKey: toValue(threadKey) });
24
+ };
25
+ return { cancel, pending: runMutation.pending, run, status, thread };
26
+ };
27
+
28
+ export { NO_MUTATION_REF, useAgent };
@@ -0,0 +1,108 @@
1
+ import { ref, computed, toValue } from 'vue';
2
+ import { NO_MUTATION_REF } from './useAgent-TEp9OWs3.mjs';
3
+ import { useMutation } from './useMutation-BKIwT8Jk.mjs';
4
+ import { useStream } from './useStream-DK2TNPo4.mjs';
5
+ import { useSubscription } from './useSubscription-B2s-lc5e.mjs';
6
+
7
+ const NO_STREAM_REF = { __lunoraRef: "" };
8
+ const reconcileOptimistic = (optimistic, durable) => {
9
+ const durableUserRows = durable.filter((message) => message.role === "user");
10
+ const consumed = /* @__PURE__ */ new Set();
11
+ return optimistic.filter((pending) => {
12
+ for (let index = pending.durableUserCountAtSend; index < durableUserRows.length; index += 1) {
13
+ const row = durableUserRows[index];
14
+ if (row !== void 0 && !consumed.has(index) && row.content === pending.content) {
15
+ consumed.add(index);
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ });
21
+ };
22
+ const useAgentChat = (options) => {
23
+ const { api, cancel: cancelReference, limit, send: sendReference, sendArgs, stream: streamReference, threadKey } = options;
24
+ const { data: history } = useSubscription(api.agents.agentMessages, () => {
25
+ const key = toValue(threadKey);
26
+ return limit === void 0 ? { key } : { key, limit };
27
+ });
28
+ const { data: threadData } = useSubscription(api.agents.agentThread, () => {
29
+ return { key: toValue(threadKey) };
30
+ });
31
+ const streamArguments = streamReference === void 0 ? "skip" : () => {
32
+ return { key: toValue(threadKey) };
33
+ };
34
+ const { chunks } = useStream(streamReference ?? NO_STREAM_REF, streamArguments);
35
+ const sendMutation = useMutation(sendReference);
36
+ const cancelMutation = useMutation(cancelReference ?? NO_MUTATION_REF);
37
+ const approvalMutation = useMutation(api.agents.agentResolveApproval);
38
+ const optimistic = ref([]);
39
+ let nextId = 0;
40
+ const thread = computed(() => threadData.value);
41
+ const status = computed(() => thread.value?.status);
42
+ const durable = computed(() => history.value ?? []);
43
+ const messages = computed(() => {
44
+ const rows = durable.value;
45
+ const visible = reconcileOptimistic(optimistic.value, rows);
46
+ if (visible.length === 0) {
47
+ return rows;
48
+ }
49
+ let maxDurableSeq = -1;
50
+ for (const message of rows) {
51
+ if (message.seq > maxDurableSeq) {
52
+ maxDurableSeq = message.seq;
53
+ }
54
+ }
55
+ return [
56
+ ...rows,
57
+ ...visible.map((pending, index) => {
58
+ return {
59
+ content: pending.content,
60
+ optimistic: true,
61
+ role: "user",
62
+ seq: maxDurableSeq + 1 + index
63
+ };
64
+ })
65
+ ];
66
+ });
67
+ const streamingText = computed(() => {
68
+ const key = toValue(threadKey);
69
+ const assistantCount = durable.value.filter((message) => message.role === "assistant").length;
70
+ return chunks.value.filter((event) => event.kind !== "progress" && event.threadKey === key && event.turn >= assistantCount).map((delta) => delta.text).join("");
71
+ });
72
+ const send = async (input, arguments_) => {
73
+ const id = nextId;
74
+ nextId += 1;
75
+ const durableUserCountAtSend = durable.value.filter((message) => message.role === "user").length;
76
+ optimistic.value = [...reconcileOptimistic(optimistic.value, durable.value), { content: input, durableUserCountAtSend, id }];
77
+ try {
78
+ await sendMutation.mutate({ input, threadKey: toValue(threadKey), ...sendArgs, ...arguments_ });
79
+ } catch (error) {
80
+ optimistic.value = optimistic.value.filter((pending) => pending.id !== id);
81
+ throw error;
82
+ }
83
+ };
84
+ const approve = async (toolCallId, note) => {
85
+ const instanceId = thread.value?.instanceId;
86
+ if (instanceId === void 0) {
87
+ throw new Error("useAgentChat: cannot approve — no in-flight run (thread has no instanceId)");
88
+ }
89
+ await approvalMutation.mutate({ decision: "approve", instanceId, threadKey: toValue(threadKey), toolCallId, ...note === void 0 ? {} : { note } });
90
+ };
91
+ const reject = async (toolCallId, note) => {
92
+ const instanceId = thread.value?.instanceId;
93
+ if (instanceId === void 0) {
94
+ throw new Error("useAgentChat: cannot reject — no in-flight run (thread has no instanceId)");
95
+ }
96
+ await approvalMutation.mutate({ decision: "reject", instanceId, threadKey: toValue(threadKey), toolCallId, ...note === void 0 ? {} : { note } });
97
+ };
98
+ const cancel = async () => {
99
+ const instanceId = thread.value?.instanceId;
100
+ if (cancelReference === void 0 || instanceId === void 0) {
101
+ return;
102
+ }
103
+ await cancelMutation.mutate({ instanceId, threadKey: toValue(threadKey) });
104
+ };
105
+ return { approve, cancel, messages, reject, send, status, streamingText };
106
+ };
107
+
108
+ export { useAgentChat };
@@ -0,0 +1,12 @@
1
+ import { computed, toValue } from 'vue';
2
+ import { useSubscription } from './useSubscription-B2s-lc5e.mjs';
3
+
4
+ const useAgentState = (options) => {
5
+ const { data, error } = useSubscription(options.api.agents.agentState, () => {
6
+ return { key: toValue(options.threadKey) };
7
+ });
8
+ const state = computed(() => data.value);
9
+ return { error, state };
10
+ };
11
+
12
+ export { useAgentState };
@@ -0,0 +1,62 @@
1
+ import { computed, toValue } from 'vue';
2
+ import { useStream } from './useStream-DK2TNPo4.mjs';
3
+ import { useSubscription } from './useSubscription-B2s-lc5e.mjs';
4
+
5
+ const NO_STREAM_REF = { __lunoraRef: "" };
6
+ const EMPTY_MESSAGES = [];
7
+ const toDurableEvent = (message) => {
8
+ if (message.role === "assistant" && message.toolCalls) {
9
+ return message.toolCalls.map((call) => {
10
+ return { input: call.input, seq: message.seq, toolCallId: call.id, toolName: call.name, type: "call" };
11
+ });
12
+ }
13
+ if (message.role !== "tool") {
14
+ return void 0;
15
+ }
16
+ if (message.status === "awaiting_approval") {
17
+ return [
18
+ {
19
+ seq: message.seq,
20
+ type: "awaiting-approval",
21
+ ...message.toolCallId === void 0 ? {} : { toolCallId: message.toolCallId },
22
+ ...message.toolName === void 0 ? {} : { toolName: message.toolName }
23
+ }
24
+ ];
25
+ }
26
+ return [
27
+ {
28
+ output: message.content,
29
+ seq: message.seq,
30
+ type: "result",
31
+ ...message.status === "approved" || message.status === "rejected" ? { status: message.status } : {},
32
+ ...message.toolCallId === void 0 ? {} : { toolCallId: message.toolCallId },
33
+ ...message.toolName === void 0 ? {} : { toolName: message.toolName }
34
+ }
35
+ ];
36
+ };
37
+ const useAgentToolEvents = (options) => {
38
+ const { api, limit, stream: streamReference, threadKey } = options;
39
+ const messagesArguments = () => {
40
+ const key = toValue(threadKey);
41
+ return limit === void 0 ? { key } : { key, limit };
42
+ };
43
+ const { data: history } = useSubscription(api.agents.agentMessages, messagesArguments);
44
+ const streamArguments = streamReference === void 0 ? "skip" : () => {
45
+ return { key: toValue(threadKey) };
46
+ };
47
+ const { chunks } = useStream(streamReference ?? NO_STREAM_REF, streamArguments);
48
+ const events = computed(() => {
49
+ const key = toValue(threadKey);
50
+ const durable = history.value ?? EMPTY_MESSAGES;
51
+ const derived = durable.flatMap((message) => toDurableEvent(message) ?? []);
52
+ for (const event of chunks.value) {
53
+ if (event.kind === "progress" && event.threadKey === key) {
54
+ derived.push({ data: event.data, toolCallId: event.toolCallId, type: "progress" });
55
+ }
56
+ }
57
+ return derived;
58
+ });
59
+ return { events };
60
+ };
61
+
62
+ export { useAgentToolEvents };
@@ -1,6 +1,6 @@
1
1
  import { getIdentityStore } from '@lunora/client/auth';
2
2
  import { ref, onScopeDispose, readonly } from 'vue';
3
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
3
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
4
 
5
5
  const useAuth = () => {
6
6
  const client = useLunora();
@@ -1,5 +1,5 @@
1
1
  import { shallowRef, getCurrentScope, onScopeDispose } from 'vue';
2
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
2
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
3
3
 
4
4
  const useConnectionStatus = () => {
5
5
  const client = useLunora();
@@ -0,0 +1,70 @@
1
+ import { shallowRef, watch, toValue } from 'vue';
2
+ import { s as stableStringify } from './stable-key-C8JHuJL7.mjs';
3
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
+
5
+ const FLAGS_EVAL_PATH = "__lunora_flags__:eval";
6
+ const flagKind = (value) => {
7
+ const kind = typeof value;
8
+ if (kind === "boolean" || kind === "number" || kind === "string") {
9
+ return kind;
10
+ }
11
+ return "object";
12
+ };
13
+ const flagsReference = { __lunoraRef: FLAGS_EVAL_PATH };
14
+ const serializeContext = (context) => context === void 0 ? "" : stableStringify(context);
15
+ const useFlag = (key, defaultValue, context) => {
16
+ const client = useLunora();
17
+ const type = flagKind(defaultValue);
18
+ const value = shallowRef(defaultValue);
19
+ watch(
20
+ () => `${toValue(key)}\0${serializeContext(toValue(context))}`,
21
+ (_serialized, _previous, onCleanup) => {
22
+ const currentKey = toValue(key);
23
+ const currentContext = toValue(context);
24
+ value.value = defaultValue;
25
+ let unsubscribe;
26
+ try {
27
+ unsubscribe = client.subscribe(flagsReference, { context: currentContext, default: defaultValue, key: currentKey, type }, (next) => {
28
+ value.value = next;
29
+ });
30
+ } catch {
31
+ return;
32
+ }
33
+ onCleanup(unsubscribe);
34
+ },
35
+ { immediate: true }
36
+ );
37
+ return value;
38
+ };
39
+ const useFlags = (flags, context) => {
40
+ const client = useLunora();
41
+ const values = shallowRef(flags);
42
+ const spec = stableStringify(flags);
43
+ watch(
44
+ () => `${spec}\0${serializeContext(toValue(context))}`,
45
+ (_serialized, _previous, onCleanup) => {
46
+ const currentContext = toValue(context);
47
+ values.value = flags;
48
+ const unsubscribes = [];
49
+ for (const [key, defaultValue] of Object.entries(flags)) {
50
+ try {
51
+ unsubscribes.push(
52
+ client.subscribe(flagsReference, { context: currentContext, default: defaultValue, key, type: flagKind(defaultValue) }, (next) => {
53
+ values.value = { ...values.value, [key]: next };
54
+ })
55
+ );
56
+ } catch {
57
+ }
58
+ }
59
+ onCleanup(() => {
60
+ for (const unsubscribe of unsubscribes) {
61
+ unsubscribe();
62
+ }
63
+ });
64
+ },
65
+ { immediate: true }
66
+ );
67
+ return values;
68
+ };
69
+
70
+ export { useFlag, useFlags };
@@ -1,8 +1,109 @@
1
1
  import { shallowRef, watch, toValue, onScopeDispose, computed } from 'vue';
2
2
  import { initialPages, derivePaginationStatus, rebalance, applyLoadMore } from '@lunora/client/pagination';
3
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
3
+ import { s as stableStringify } from './stable-key-C8JHuJL7.mjs';
4
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
5
 
5
- const buildPageKey = (functionPath, pageArgs) => `${functionPath}::${JSON.stringify(pageArgs)}`;
6
+ const toBase64 = (bytes) => {
7
+ let binary = "";
8
+ const chunk = 32768;
9
+ for (let index = 0; index < bytes.length; index += chunk) {
10
+ binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
11
+ }
12
+ return btoa(binary);
13
+ };
14
+
15
+ const TAG = "$lunora.wire$";
16
+ const MAX_DEPTH = 64;
17
+ const encodeWire = (value, depth = 0) => {
18
+ if (depth > MAX_DEPTH) {
19
+ throw new RangeError(`wire-codec: value nesting exceeds the ${MAX_DEPTH}-level limit`);
20
+ }
21
+ if (value === void 0) {
22
+ return [TAG, "undefined"];
23
+ }
24
+ if (value === null) {
25
+ return null;
26
+ }
27
+ const kind = typeof value;
28
+ if (kind === "bigint") {
29
+ return [TAG, "bigint", value.toString()];
30
+ }
31
+ if (kind === "number") {
32
+ const numeric = value;
33
+ if (Number.isNaN(numeric)) {
34
+ return [TAG, "nan"];
35
+ }
36
+ if (numeric === Infinity) {
37
+ return [TAG, "inf"];
38
+ }
39
+ if (numeric === -Infinity) {
40
+ return [TAG, "-inf"];
41
+ }
42
+ return numeric;
43
+ }
44
+ if (kind !== "object") {
45
+ return value;
46
+ }
47
+ if (value instanceof Date) {
48
+ return [TAG, "date", encodeWire(value.getTime(), depth + 1)];
49
+ }
50
+ if (value instanceof Error) {
51
+ const error = value;
52
+ const properties = {};
53
+ for (const key of Object.keys(error)) {
54
+ if (error[key] !== void 0) {
55
+ properties[key] = encodeWire(error[key], depth + 1);
56
+ }
57
+ }
58
+ const encodedError = [TAG, "error", error.name, error.message, properties];
59
+ if (error.cause !== void 0) {
60
+ encodedError.push(encodeWire(error.cause, depth + 1));
61
+ }
62
+ return encodedError;
63
+ }
64
+ if (value instanceof URL) {
65
+ return [TAG, "url", value.href];
66
+ }
67
+ if (value instanceof Map) {
68
+ return [TAG, "map", [...value.entries()].map(([k, v]) => [encodeWire(k, depth + 1), encodeWire(v, depth + 1)])];
69
+ }
70
+ if (value instanceof Set) {
71
+ return [TAG, "set", [...value].map((item) => encodeWire(item, depth + 1))];
72
+ }
73
+ if (value instanceof ArrayBuffer) {
74
+ return [TAG, "bytes", toBase64(new Uint8Array(value)), "ArrayBuffer"];
75
+ }
76
+ if (ArrayBuffer.isView(value)) {
77
+ const view = value;
78
+ const ctorName = view.constructor.name;
79
+ const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
80
+ return ctorName === "Uint8Array" ? [TAG, "bytes", toBase64(bytes)] : [TAG, "bytes", toBase64(bytes), ctorName];
81
+ }
82
+ if (Array.isArray(value)) {
83
+ const encoded = value.map((item) => encodeWire(item, depth + 1));
84
+ return encoded.length > 0 && encoded[0] === TAG ? [TAG, "arr", encoded] : encoded;
85
+ }
86
+ const proto = Object.getPrototypeOf(value);
87
+ if (proto !== null && proto !== Object.prototype) {
88
+ const name = value.constructor?.name ?? "value";
89
+ throw new TypeError(
90
+ `wire-codec: cannot encode a ${name} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`
91
+ );
92
+ }
93
+ const source = value;
94
+ const result = {};
95
+ for (const key of Object.keys(source)) {
96
+ const field = source[key];
97
+ if (field !== void 0) {
98
+ result[key] = encodeWire(field, depth + 1);
99
+ }
100
+ }
101
+ return result;
102
+ };
103
+
104
+ const stableWireKey = (value) => stableStringify(encodeWire(value));
105
+
106
+ const buildPageKey = (functionPath, pageArgs) => `${functionPath}::${stableWireKey(pageArgs)}`;
6
107
  const buildPageArgs = (page, baseArgs) => {
7
108
  return {
8
109
  ...baseArgs,
@@ -14,6 +115,7 @@ const usePaginatedCore = (function_, args, options) => {
14
115
  const { initialNumItems, shardKey } = options;
15
116
  const pages = shallowRef(initialPages(initialNumItems));
16
117
  const pageResults = shallowRef([]);
118
+ let lastLoadMoreCursor;
17
119
  const activeSubs = /* @__PURE__ */ new Map();
18
120
  const resultsByKey = /* @__PURE__ */ new Map();
19
121
  const pendingPageKeys = /* @__PURE__ */ new Set();
@@ -49,6 +151,7 @@ const usePaginatedCore = (function_, args, options) => {
49
151
  entry.unsub();
50
152
  activeSubs.delete(originalKey);
51
153
  resultsByKey.delete(entry.currentKey);
154
+ pendingPageKeys.delete(entry.currentKey);
52
155
  }
53
156
  }
54
157
  const coveredKeys = new Set([...activeSubs.values()].map((subEntry) => subEntry.currentKey));
@@ -93,13 +196,16 @@ const usePaginatedCore = (function_, args, options) => {
93
196
  }
94
197
  activeSubs.clear();
95
198
  resultsByKey.clear();
199
+ pendingPageKeys.clear();
96
200
  };
97
201
  watch(
98
- () => toValue(args),
99
- (current) => {
202
+ () => stableWireKey(toValue(args)),
203
+ () => {
204
+ const current = toValue(args);
100
205
  teardownAll();
101
206
  pages.value = initialPages(initialNumItems);
102
207
  pageResults.value = [];
208
+ lastLoadMoreCursor = void 0;
103
209
  if (current !== "skip") {
104
210
  syncSubscriptions(pages.value, current);
105
211
  rebuildPageResults(pages.value, current);
@@ -135,10 +241,14 @@ const usePaginatedCore = (function_, args, options) => {
135
241
  if (currentStatus !== "CanLoadMore") {
136
242
  return;
137
243
  }
244
+ if (nextCursor === lastLoadMoreCursor) {
245
+ return;
246
+ }
138
247
  const next = applyLoadMore(pages.value, nextCursor, numberItems);
139
248
  if (!next) {
140
249
  return;
141
250
  }
251
+ lastLoadMoreCursor = nextCursor;
142
252
  const oldTail = pages.value.at(-1);
143
253
  const newPinnedPage = next.at(-2);
144
254
  if (oldTail && newPinnedPage) {
@@ -1,6 +1,6 @@
1
1
  import { createMutationRunner } from '@lunora/client';
2
2
  import { shallowRef, ref } from 'vue';
3
- import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
3
+ import { useLunora } from './LUNORA_INJECTION_KEY-DQM5MgsC.mjs';
4
4
 
5
5
  const useMutation = (function_) => {
6
6
  const client = useLunora();
@@ -0,0 +1,19 @@
1
+ import { createMutatorRunner } from '@lunora/client';
2
+ import { shallowRef, computed } from 'vue';
3
+
4
+ const useMutator = (handle) => {
5
+ const error = shallowRef(void 0);
6
+ const pending = shallowRef(false);
7
+ const isError = computed(() => error.value !== void 0);
8
+ const { mutate, reset } = createMutatorRunner(handle, {
9
+ setError: (value) => {
10
+ error.value = value;
11
+ },
12
+ setPending: (value) => {
13
+ pending.value = value;
14
+ }
15
+ });
16
+ return { error, isError, mutate, pending, reset };
17
+ };
18
+
19
+ export { useMutator };