@lunora/react 0.0.0 → 1.0.0-alpha.2

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 (31) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +150 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.mts +499 -0
  5. package/dist/index.d.ts +499 -0
  6. package/dist/index.mjs +17 -0
  7. package/dist/packem_shared/Authenticated-DtKgZT2Z.mjs +33 -0
  8. package/dist/packem_shared/CheckoutButton-CVSry8U1.mjs +185 -0
  9. package/dist/packem_shared/LunoraProvider-D38Xp16l.mjs +80 -0
  10. package/dist/packem_shared/cache-CItk3fgN.mjs +75 -0
  11. package/dist/packem_shared/hydratePreloaded-BlFL9FGq.mjs +46 -0
  12. package/dist/packem_shared/lunoraQueryOptions-CsuWzjg1.mjs +16 -0
  13. package/dist/packem_shared/query-key-C5rufkEE.mjs +21 -0
  14. package/dist/packem_shared/query-options.d-D4okOpO8.d.mts +38 -0
  15. package/dist/packem_shared/query-options.d-D4okOpO8.d.ts +38 -0
  16. package/dist/packem_shared/use-paginated-core-CoOfcc-p.mjs +161 -0
  17. package/dist/packem_shared/useAuth-CNUKtOOp.mjs +129 -0
  18. package/dist/packem_shared/useAuthState-BiGhtSCs.mjs +36 -0
  19. package/dist/packem_shared/useConnectionStatus-DRSY9ldm.mjs +30 -0
  20. package/dist/packem_shared/useInfiniteQuery-MH0x4l8h.mjs +97 -0
  21. package/dist/packem_shared/useMutation-CrvMXRsk.mjs +67 -0
  22. package/dist/packem_shared/usePaginatedQuery-D3PTDRGS.mjs +46 -0
  23. package/dist/packem_shared/usePresence-D7jLuxj0.mjs +108 -0
  24. package/dist/packem_shared/useQuery-C5S0W-7K.mjs +41 -0
  25. package/dist/packem_shared/useRateLimit-DTEffQEi.mjs +64 -0
  26. package/dist/packem_shared/useStream-BRY9nemd.mjs +125 -0
  27. package/dist/packem_shared/useSubscription-CHMCjyQg.mjs +139 -0
  28. package/dist/server.d.mts +51 -0
  29. package/dist/server.d.ts +51 -0
  30. package/dist/server.mjs +31 -0
  31. package/package.json +60 -17
@@ -0,0 +1,129 @@
1
+ 'use client';
2
+ import { c } from 'react/compiler-runtime';
3
+ import { useSyncExternalStore } from 'react';
4
+ import { useLunora } from './LunoraProvider-D38Xp16l.mjs';
5
+
6
+ const stores = /* @__PURE__ */ new WeakMap();
7
+ const createStore = (client) => {
8
+ const listeners = /* @__PURE__ */ new Set();
9
+ let user = null;
10
+ const notify = () => {
11
+ for (const listener of listeners) {
12
+ listener();
13
+ }
14
+ };
15
+ let generation = 0;
16
+ const setUser = (next) => {
17
+ if (user !== next) {
18
+ user = next;
19
+ notify();
20
+ }
21
+ };
22
+ const refresh = () => {
23
+ generation += 1;
24
+ const current = generation;
25
+ if (client.getAuthToken() === null) {
26
+ setUser(null);
27
+ return;
28
+ }
29
+ client.getCurrentUser().then((next) => {
30
+ if (current === generation) {
31
+ setUser(next);
32
+ }
33
+ return void 0;
34
+ }).catch(() => {
35
+ if (current === generation) {
36
+ setUser(null);
37
+ }
38
+ });
39
+ };
40
+ let unsubscribeToken;
41
+ return {
42
+ getSnapshot: () => user,
43
+ subscribe: (onChange) => {
44
+ const firstSubscriber = listeners.size === 0;
45
+ listeners.add(onChange);
46
+ if (firstSubscriber) {
47
+ unsubscribeToken = client.onAuthTokenChange(refresh);
48
+ refresh();
49
+ }
50
+ return () => {
51
+ listeners.delete(onChange);
52
+ if (listeners.size === 0) {
53
+ unsubscribeToken?.();
54
+ unsubscribeToken = void 0;
55
+ }
56
+ };
57
+ }
58
+ };
59
+ };
60
+ const getStore = (client) => {
61
+ let store = stores.get(client);
62
+ if (!store) {
63
+ store = createStore(client);
64
+ stores.set(client, store);
65
+ }
66
+ return store;
67
+ };
68
+ const useAuth = () => {
69
+ const $ = c(12);
70
+ const client = useLunora();
71
+ let t0;
72
+ if ($[0] !== client) {
73
+ t0 = getStore(client);
74
+ $[0] = client;
75
+ $[1] = t0;
76
+ } else {
77
+ t0 = $[1];
78
+ }
79
+ const store = t0;
80
+ let t1;
81
+ let t2;
82
+ let t3;
83
+ if ($[2] !== client) {
84
+ t1 = (onChange) => client.onAuthTokenChange(onChange);
85
+ t2 = () => client.getAuthToken();
86
+ t3 = () => client.getAuthToken();
87
+ $[2] = client;
88
+ $[3] = t1;
89
+ $[4] = t2;
90
+ $[5] = t3;
91
+ } else {
92
+ t1 = $[3];
93
+ t2 = $[4];
94
+ t3 = $[5];
95
+ }
96
+ const token = useSyncExternalStore(t1, t2, t3);
97
+ const user = useSyncExternalStore(store.subscribe, store.getSnapshot, _temp);
98
+ let t4;
99
+ if ($[6] !== client) {
100
+ t4 = (next) => {
101
+ client.setAuthToken(next);
102
+ };
103
+ $[6] = client;
104
+ $[7] = t4;
105
+ } else {
106
+ t4 = $[7];
107
+ }
108
+ const setToken = t4;
109
+ let t5;
110
+ if ($[8] !== setToken || $[9] !== token || $[10] !== user) {
111
+ t5 = {
112
+ setToken,
113
+ token,
114
+ user
115
+ };
116
+ $[8] = setToken;
117
+ $[9] = token;
118
+ $[10] = user;
119
+ $[11] = t5;
120
+ } else {
121
+ t5 = $[11];
122
+ }
123
+ return t5;
124
+ };
125
+ function _temp() {
126
+ return null;
127
+ }
128
+
129
+ export { useAuth as default };
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { c } from 'react/compiler-runtime';
3
+ import { useSyncExternalStore } from 'react';
4
+ import useAuth from './useAuth-CNUKtOOp.mjs';
5
+
6
+ const subscribe = () => () => void 0;
7
+ const useAuthState = () => {
8
+ const $ = c(3);
9
+ const {
10
+ token
11
+ } = useAuth();
12
+ const hydrated = useSyncExternalStore(subscribe, _temp, _temp2);
13
+ const t0 = hydrated && token !== null;
14
+ const t1 = !hydrated;
15
+ let t2;
16
+ if ($[0] !== t0 || $[1] !== t1) {
17
+ t2 = {
18
+ isAuthenticated: t0,
19
+ isLoading: t1
20
+ };
21
+ $[0] = t0;
22
+ $[1] = t1;
23
+ $[2] = t2;
24
+ } else {
25
+ t2 = $[2];
26
+ }
27
+ return t2;
28
+ };
29
+ function _temp() {
30
+ return true;
31
+ }
32
+ function _temp2() {
33
+ return false;
34
+ }
35
+
36
+ export { useAuthState };
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { c } from 'react/compiler-runtime';
3
+ import { useSyncExternalStore } from 'react';
4
+ import { useLunora } from './LunoraProvider-D38Xp16l.mjs';
5
+
6
+ const useConnectionStatus = () => {
7
+ const $ = c(4);
8
+ const client = useLunora();
9
+ let t0;
10
+ let t1;
11
+ let t2;
12
+ if ($[0] !== client) {
13
+ t0 = (onChange) => client.onConnectionStatus(() => {
14
+ onChange();
15
+ });
16
+ t1 = () => client.connectionStatus();
17
+ t2 = () => client.connectionStatus();
18
+ $[0] = client;
19
+ $[1] = t0;
20
+ $[2] = t1;
21
+ $[3] = t2;
22
+ } else {
23
+ t0 = $[1];
24
+ t1 = $[2];
25
+ t2 = $[3];
26
+ }
27
+ return useSyncExternalStore(t0, t1, t2);
28
+ };
29
+
30
+ export { useConnectionStatus as default };
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+ import { c } from 'react/compiler-runtime';
3
+ import { useRef, useEffect } from 'react';
4
+ import { u as usePaginatedCore } from './use-paginated-core-CoOfcc-p.mjs';
5
+
6
+ const useInfiniteQuery = (function_, args, options) => {
7
+ const $ = c(15);
8
+ const {
9
+ initialNumItems
10
+ } = options;
11
+ const {
12
+ loadMore,
13
+ pageResults,
14
+ status
15
+ } = usePaginatedCore(function_, args === "skip" ? "skip" : args, options);
16
+ const skipped = args === "skip";
17
+ let resolvedPages;
18
+ if ($[0] !== pageResults) {
19
+ resolvedPages = [];
20
+ for (const result of pageResults) {
21
+ if (result) {
22
+ resolvedPages.push(result.page);
23
+ }
24
+ }
25
+ $[0] = pageResults;
26
+ $[1] = resolvedPages;
27
+ } else {
28
+ resolvedPages = $[1];
29
+ }
30
+ let t0;
31
+ if ($[2] !== initialNumItems || $[3] !== loadMore) {
32
+ t0 = {
33
+ defaultNumItems: initialNumItems,
34
+ loadMore
35
+ };
36
+ $[2] = initialNumItems;
37
+ $[3] = loadMore;
38
+ $[4] = t0;
39
+ } else {
40
+ t0 = $[4];
41
+ }
42
+ const nextRef = useRef(t0);
43
+ let t1;
44
+ if ($[5] !== initialNumItems || $[6] !== loadMore) {
45
+ t1 = () => {
46
+ nextRef.current = {
47
+ defaultNumItems: initialNumItems,
48
+ loadMore
49
+ };
50
+ };
51
+ $[5] = initialNumItems;
52
+ $[6] = loadMore;
53
+ $[7] = t1;
54
+ } else {
55
+ t1 = $[7];
56
+ }
57
+ useEffect(t1);
58
+ let t2;
59
+ if ($[8] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
60
+ t2 = (numberItems) => {
61
+ const {
62
+ defaultNumItems,
63
+ loadMore: load
64
+ } = nextRef.current;
65
+ load(numberItems ?? defaultNumItems);
66
+ };
67
+ $[8] = t2;
68
+ } else {
69
+ t2 = $[8];
70
+ }
71
+ const fetchNextPage = t2;
72
+ const t3 = status === "CanLoadMore";
73
+ const t4 = !skipped && status === "LoadingMore";
74
+ const t5 = !skipped && status === "LoadingFirstPage";
75
+ let t6;
76
+ if ($[9] !== resolvedPages || $[10] !== status || $[11] !== t3 || $[12] !== t4 || $[13] !== t5) {
77
+ t6 = {
78
+ fetchNextPage,
79
+ hasNextPage: t3,
80
+ isFetchingNextPage: t4,
81
+ isLoading: t5,
82
+ pages: resolvedPages,
83
+ status
84
+ };
85
+ $[9] = resolvedPages;
86
+ $[10] = status;
87
+ $[11] = t3;
88
+ $[12] = t4;
89
+ $[13] = t5;
90
+ $[14] = t6;
91
+ } else {
92
+ t6 = $[14];
93
+ }
94
+ return t6;
95
+ };
96
+
97
+ export { useInfiniteQuery as default };
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { useMutation as useMutation$1 } from '@tanstack/react-query';
3
+ import { useRef, useState, useCallback } from 'react';
4
+ import { useLunora } from './LunoraProvider-D38Xp16l.mjs';
5
+
6
+ const useMutation = (function_) => {
7
+ const client = useLunora();
8
+ const pendingCountRef = useRef(0);
9
+ const [pending, setPending] = useState(false);
10
+ const mutation = useMutation$1({
11
+ mutationFn: ({
12
+ args,
13
+ options
14
+ }) => client.mutation(function_, args, options),
15
+ // `onMutate` fires when a call starts, `onSettled` when it resolves or
16
+ // rejects — so overlapping calls compose and `pending` only clears once
17
+ // the last one settles.
18
+ onMutate: () => {
19
+ pendingCountRef.current += 1;
20
+ setPending(true);
21
+ },
22
+ onSettled: () => {
23
+ pendingCountRef.current -= 1;
24
+ setPending(pendingCountRef.current > 0);
25
+ }
26
+ });
27
+ const {
28
+ data,
29
+ error,
30
+ isError,
31
+ mutateAsync,
32
+ reset
33
+ } = mutation;
34
+ const mutate = useCallback((args_0, options_0) => mutateAsync({
35
+ args: args_0,
36
+ options: options_0
37
+ }), [mutateAsync]);
38
+ const withOptimisticUpdate = useCallback((update) => {
39
+ const boundMutate = (args_1, options_1) => mutateAsync({
40
+ args: args_1,
41
+ options: {
42
+ optimisticUpdate: update,
43
+ ...options_1
44
+ }
45
+ });
46
+ return {
47
+ data,
48
+ error,
49
+ isError,
50
+ mutate: boundMutate,
51
+ pending,
52
+ reset,
53
+ withOptimisticUpdate
54
+ };
55
+ }, [mutateAsync, data, error, isError, pending, reset]);
56
+ return {
57
+ data,
58
+ error,
59
+ isError,
60
+ mutate,
61
+ pending,
62
+ reset,
63
+ withOptimisticUpdate
64
+ };
65
+ };
66
+
67
+ export { useMutation };
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+ import { c } from 'react/compiler-runtime';
3
+ import { u as usePaginatedCore } from './use-paginated-core-CoOfcc-p.mjs';
4
+
5
+ const usePaginatedQuery = (function_, args, options) => {
6
+ const $ = c(7);
7
+ const {
8
+ loadMore,
9
+ pageResults,
10
+ status
11
+ } = usePaginatedCore(function_, args === "skip" ? "skip" : args, options);
12
+ let results;
13
+ if ($[0] !== pageResults) {
14
+ results = [];
15
+ for (const result of pageResults) {
16
+ if (result) {
17
+ results.push(...result.page);
18
+ }
19
+ }
20
+ $[0] = pageResults;
21
+ $[1] = results;
22
+ } else {
23
+ results = $[1];
24
+ }
25
+ const skipped = args === "skip";
26
+ const t0 = !skipped && (status === "LoadingFirstPage" || status === "LoadingMore");
27
+ let t1;
28
+ if ($[2] !== loadMore || $[3] !== results || $[4] !== status || $[5] !== t0) {
29
+ t1 = {
30
+ isLoading: t0,
31
+ loadMore,
32
+ results,
33
+ status
34
+ };
35
+ $[2] = loadMore;
36
+ $[3] = results;
37
+ $[4] = status;
38
+ $[5] = t0;
39
+ $[6] = t1;
40
+ } else {
41
+ t1 = $[6];
42
+ }
43
+ return t1;
44
+ };
45
+
46
+ export { usePaginatedQuery };
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+ import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useLunora } from './LunoraProvider-D38Xp16l.mjs';
4
+
5
+ const makeSessionId = () => {
6
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
7
+ return crypto.randomUUID();
8
+ }
9
+ return `sess-${Math.random().toString(36).slice(2)}-${String(Date.now())}`;
10
+ };
11
+ const DEFAULT_INTERVAL_MS = 1e4;
12
+ const usePresence = (roomId, options) => {
13
+ const client = useLunora();
14
+ const {
15
+ heartbeat,
16
+ intervalMs = DEFAULT_INTERVAL_MS,
17
+ listPresent,
18
+ shardKey
19
+ } = options;
20
+ const generatedSessionId = useMemo(() => options.sessionId ?? makeSessionId(), [options.sessionId]);
21
+ const [present, setPresent] = useState(void 0);
22
+ const dataRef = useRef(options.data);
23
+ const inputsRef = useRef({
24
+ client,
25
+ heartbeat,
26
+ roomId,
27
+ sessionId: generatedSessionId,
28
+ shardKey
29
+ });
30
+ useEffect(() => {
31
+ inputsRef.current = {
32
+ client,
33
+ heartbeat,
34
+ roomId,
35
+ sessionId: generatedSessionId,
36
+ shardKey
37
+ };
38
+ });
39
+ const sendHeartbeat = useCallback(() => {
40
+ const {
41
+ client: c,
42
+ heartbeat: hb,
43
+ roomId: room,
44
+ sessionId: sid,
45
+ shardKey: sk
46
+ } = inputsRef.current;
47
+ const args = {
48
+ roomId: room,
49
+ sessionId: sid,
50
+ ...dataRef.current === void 0 ? {} : {
51
+ data: dataRef.current
52
+ }
53
+ };
54
+ c.mutation(hb, args, {
55
+ shardKey: sk
56
+ }).catch(() => void 0);
57
+ }, []);
58
+ const setData = useCallback((next) => {
59
+ dataRef.current = next;
60
+ sendHeartbeat();
61
+ }, [sendHeartbeat]);
62
+ useEffect(() => {
63
+ sendHeartbeat();
64
+ const handle = setInterval(sendHeartbeat, intervalMs);
65
+ const onVisible = () => {
66
+ if (document.visibilityState === "visible") {
67
+ sendHeartbeat();
68
+ }
69
+ };
70
+ document.addEventListener("visibilitychange", onVisible);
71
+ return () => {
72
+ clearInterval(handle);
73
+ document.removeEventListener("visibilitychange", onVisible);
74
+ };
75
+ }, [sendHeartbeat, intervalMs]);
76
+ useEffect(() => {
77
+ const release = client.acquireConnectionContext({
78
+ roomId,
79
+ sessionId: generatedSessionId
80
+ }, {
81
+ shardKey
82
+ });
83
+ return release;
84
+ }, [client, roomId, generatedSessionId, shardKey]);
85
+ useEffect(() => {
86
+ let cancelled = false;
87
+ const unsubscribe = client.subscribe(listPresent, {
88
+ roomId
89
+ }, (value) => {
90
+ if (!cancelled) {
91
+ setPresent(value);
92
+ }
93
+ }, {
94
+ shardKey
95
+ });
96
+ return () => {
97
+ cancelled = true;
98
+ unsubscribe();
99
+ };
100
+ }, [client, listPresent.__lunoraRef, roomId, shardKey]);
101
+ return {
102
+ present,
103
+ sessionId: generatedSessionId,
104
+ setData
105
+ };
106
+ };
107
+
108
+ export { usePresence };
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { useQueryClient, useQuery as useQuery$1 } from '@tanstack/react-query';
3
+ import { useMemo, useEffect } from 'react';
4
+ import { g as getSubscriptionRegistry } from './cache-CItk3fgN.mjs';
5
+ import { useLunora } from './LunoraProvider-D38Xp16l.mjs';
6
+ import { l as lunoraQueryKey, a as stableStringify, s as serializeQueryKey } from './query-key-C5rufkEE.mjs';
7
+
8
+ const useQuery = (function_, args, options = {}) => {
9
+ const client = useLunora();
10
+ const queryClient = useQueryClient();
11
+ const {
12
+ shardKey
13
+ } = options;
14
+ const skipped = args === "skip";
15
+ const argsRecord = skipped ? {} : args;
16
+ const queryKey = useMemo(() => lunoraQueryKey(function_, argsRecord, shardKey), [function_.__lunoraRef, stableStringify(argsRecord), shardKey]);
17
+ const {
18
+ data
19
+ } = useQuery$1({
20
+ enabled: !skipped,
21
+ queryFn: () => client.query(function_, argsRecord, {
22
+ shardKey
23
+ }),
24
+ queryKey,
25
+ // Lunora is push-driven: once the initial fetch resolves, the WS owns
26
+ // freshness. Staleness only matters when the subscription is missing,
27
+ // and the registry handles that with a polling fallback.
28
+ staleTime: Number.POSITIVE_INFINITY
29
+ });
30
+ useEffect(() => {
31
+ if (skipped) {
32
+ return () => {
33
+ };
34
+ }
35
+ const registry = getSubscriptionRegistry(client);
36
+ return registry.attach(queryClient, queryKey, function_, argsRecord, shardKey);
37
+ }, [client, queryClient, serializeQueryKey(queryKey), skipped]);
38
+ return data;
39
+ };
40
+
41
+ export { useQuery as default };
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ import { evaluate } from '@lunora/ratelimit';
3
+ import { useRef, useReducer, useCallback, useEffect } from 'react';
4
+
5
+ const useRateLimit = (config, options = {}) => {
6
+ const now = options.now ?? Date.now;
7
+ const tickMs = options.tickMs ?? 1e3;
8
+ const valueRef = useRef(void 0);
9
+ const [, forceRender] = useReducer((count) => count + 1, 0);
10
+ const consume = useCallback((count_0 = 1) => {
11
+ const {
12
+ status,
13
+ value
14
+ } = evaluate(config, valueRef.current, {
15
+ consume: true,
16
+ count: count_0,
17
+ now: now(),
18
+ reserve: false
19
+ });
20
+ if (value !== void 0) {
21
+ valueRef.current = value;
22
+ }
23
+ forceRender();
24
+ return status;
25
+ }, [config, now]);
26
+ const check = useCallback((count_1 = 1) => evaluate(config, valueRef.current, {
27
+ consume: false,
28
+ count: count_1,
29
+ now: now(),
30
+ reserve: false
31
+ }).status.ok, [config, now]);
32
+ const reset = useCallback(() => {
33
+ valueRef.current = void 0;
34
+ forceRender();
35
+ }, []);
36
+ const {
37
+ status: status_0
38
+ } = evaluate(config, valueRef.current, {
39
+ consume: false,
40
+ count: 1,
41
+ now: now(),
42
+ reserve: false
43
+ });
44
+ useEffect(() => {
45
+ if (status_0.ok) {
46
+ return () => {
47
+ };
48
+ }
49
+ const handle = setInterval(forceRender, tickMs);
50
+ return () => {
51
+ clearInterval(handle);
52
+ };
53
+ }, [status_0.ok, tickMs]);
54
+ return {
55
+ check,
56
+ consume,
57
+ disabled: !status_0.ok,
58
+ ok: status_0.ok,
59
+ reset,
60
+ retryAfter: status_0.retryAfter
61
+ };
62
+ };
63
+
64
+ export { useRateLimit };