@lunora/angular 1.0.0-alpha.5 → 1.0.0-alpha.7

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 CHANGED
@@ -2,12 +2,12 @@ export { auth } from './packem_shared/auth-Df9N87Z4.mjs';
2
2
  export { LUNORA_CLIENT, injectLunoraClient, provideLunora } from './packem_shared/LUNORA_CLIENT-DHUfNu9x.mjs';
3
3
  export { connectionStatus } from './packem_shared/connectionStatus-BlLodleK.mjs';
4
4
  export { flag, flags } from './packem_shared/flag-CGBo90HJ.mjs';
5
- export { hydratePreloaded } from './packem_shared/hydratePreloaded-ClRc-7rL.mjs';
6
- export { liveQuery } from './packem_shared/liveQuery-D5VQBOgf.mjs';
5
+ export { hydratePreloaded } from './packem_shared/hydratePreloaded-DIpD1cAM.mjs';
6
+ export { liveQuery } from './packem_shared/liveQuery-DVxKidjM.mjs';
7
7
  export { mutate } from './packem_shared/mutate-D3rEHwbb.mjs';
8
8
  export { mutator } from './packem_shared/mutator-BHL8bakL.mjs';
9
- export { infiniteQuery, paginatedQuery } from './packem_shared/infiniteQuery-DUV60k-n.mjs';
10
- export { presence } from './packem_shared/presence-h2xonCZM.mjs';
11
- export { rateLimit } from './packem_shared/rateLimit-CKcAig4M.mjs';
12
- export { subscription } from './packem_shared/subscription-Dd9dQiBB.mjs';
9
+ export { infiniteQuery, paginatedQuery } from './packem_shared/infiniteQuery-nboKfr5E.mjs';
10
+ export { presence } from './packem_shared/presence-BTuq19dS.mjs';
11
+ export { rateLimit } from './packem_shared/rateLimit-I4kRT9qV.mjs';
12
+ export { subscription } from './packem_shared/subscription-oZ-WTmpp.mjs';
13
13
  export { SKIP } from '@lunora/client/query';
@@ -1,28 +1,32 @@
1
1
  import { inject, DestroyRef, signal } from '@angular/core';
2
2
  import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
3
+ import { s as shouldOpenSubscription } from './platform-Dg8Bppgq.mjs';
3
4
 
4
5
  const hydratePreloaded = (preloaded, options = {}) => {
5
6
  const client = resolveLunoraClient(options.client);
7
+ const fromInjectionContext = options.destroyRef === void 0;
6
8
  const destroyRef = options.destroyRef ?? inject(DestroyRef);
7
9
  const { args, functionPath, shardKey, value } = preloaded;
8
10
  const data = signal(value);
9
11
  const error = signal(void 0);
10
12
  const functionReference = { __lunoraRef: functionPath };
11
- const unsubscribe = client.subscribe(
12
- functionReference,
13
- args,
14
- (next) => {
15
- data.set(next);
16
- error.set(void 0);
17
- },
18
- {
19
- onError: (error_) => {
20
- error.set(error_);
13
+ if (shouldOpenSubscription(fromInjectionContext)) {
14
+ const unsubscribe = client.subscribe(
15
+ functionReference,
16
+ args,
17
+ (next) => {
18
+ data.set(next);
19
+ error.set(void 0);
21
20
  },
22
- shardKey
23
- }
24
- );
25
- destroyRef.onDestroy(unsubscribe);
21
+ {
22
+ onError: (error_) => {
23
+ error.set(error_);
24
+ },
25
+ shardKey
26
+ }
27
+ );
28
+ destroyRef.onDestroy(unsubscribe);
29
+ }
26
30
  return { data: data.asReadonly(), error: error.asReadonly() };
27
31
  };
28
32
 
@@ -1,6 +1,7 @@
1
1
  import { computed, inject, DestroyRef, signal } from '@angular/core';
2
- import { initialPages, rebalance, derivePaginationStatus, applyLoadMore } from '@lunora/client/pagination';
2
+ import { initialPages, derivePaginationStatus, applyLoadMore, rebalance } from '@lunora/client/pagination';
3
3
  import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
4
+ import { s as shouldOpenSubscription } from './platform-Dg8Bppgq.mjs';
4
5
 
5
6
  const buildPageKey = (functionPath, pageArgs) => `${functionPath}::${JSON.stringify(pageArgs)}`;
6
7
  const buildPageArgs = (page, baseArgs) => {
@@ -11,6 +12,7 @@ const buildPageArgs = (page, baseArgs) => {
11
12
  };
12
13
  const usePaginatedCore = (reference, baseArgs, options) => {
13
14
  const client = resolveLunoraClient(options.client);
15
+ const fromInjectionContext = options.destroyRef === void 0;
14
16
  const destroyRef = options.destroyRef ?? inject(DestroyRef);
15
17
  const { initialNumItems, shardKey } = options;
16
18
  const functionPath = reference["__lunoraRef"];
@@ -47,7 +49,7 @@ const usePaginatedCore = (reference, baseArgs, options) => {
47
49
  }
48
50
  }
49
51
  };
50
- const syncSubscriptions = (currentPages) => {
52
+ const syncPass = (currentPages) => {
51
53
  const wantedKeys = /* @__PURE__ */ new Set();
52
54
  for (const page of currentPages) {
53
55
  wantedKeys.add(buildPageKey(functionPath, buildPageArgs(page, narrowedArgs)));
@@ -102,7 +104,26 @@ const usePaginatedCore = (reference, baseArgs, options) => {
102
104
  coveredKeys.add(key);
103
105
  }
104
106
  };
105
- if (baseArgs !== "skip") {
107
+ let syncing = false;
108
+ let resyncRequested = false;
109
+ const syncSubscriptions = (currentPages) => {
110
+ if (syncing) {
111
+ resyncRequested = true;
112
+ return;
113
+ }
114
+ syncing = true;
115
+ try {
116
+ let pagesToSync = currentPages;
117
+ do {
118
+ resyncRequested = false;
119
+ syncPass(pagesToSync);
120
+ pagesToSync = pages();
121
+ } while (resyncRequested);
122
+ } finally {
123
+ syncing = false;
124
+ }
125
+ };
126
+ if (baseArgs !== "skip" && shouldOpenSubscription(fromInjectionContext)) {
106
127
  syncSubscriptions(pages());
107
128
  }
108
129
  doRebuildPageResults();
@@ -0,0 +1,32 @@
1
+ import { inject, DestroyRef, signal } from '@angular/core';
2
+ import { createQuerySubscription } from '@lunora/client/query';
3
+ import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
4
+ import { s as shouldOpenSubscription } from './platform-Dg8Bppgq.mjs';
5
+
6
+ const liveQuery = (reference, args, options = {}) => {
7
+ const client = resolveLunoraClient(options.client);
8
+ const fromInjectionContext = options.destroyRef === void 0;
9
+ const destroyRef = options.destroyRef ?? inject(DestroyRef);
10
+ const value = signal(void 0);
11
+ if (shouldOpenSubscription(fromInjectionContext)) {
12
+ const unsubscribe = createQuerySubscription(
13
+ client,
14
+ reference,
15
+ args,
16
+ {
17
+ onData: (next) => {
18
+ value.set(next);
19
+ },
20
+ onError: options.onError,
21
+ onReset: () => {
22
+ value.set(void 0);
23
+ }
24
+ },
25
+ { shardKey: options.shardKey }
26
+ );
27
+ destroyRef.onDestroy(unsubscribe);
28
+ }
29
+ return value.asReadonly();
30
+ };
31
+
32
+ export { liveQuery };
@@ -0,0 +1,14 @@
1
+ import { inject, PLATFORM_ID, NgZone } from '@angular/core';
2
+
3
+ const shouldOpenSubscription = (fromInjectionContext) => {
4
+ if (!fromInjectionContext) {
5
+ return true;
6
+ }
7
+ return inject(PLATFORM_ID, { optional: true }) !== "server";
8
+ };
9
+ const runOutsideAngular = (fromInjectionContext, register) => {
10
+ const zone = fromInjectionContext ? inject(NgZone, { optional: true }) : void 0;
11
+ return zone ? zone.runOutsideAngular(register) : register();
12
+ };
13
+
14
+ export { runOutsideAngular as r, shouldOpenSubscription as s };
@@ -0,0 +1,77 @@
1
+ import { inject, DestroyRef, signal } from '@angular/core';
2
+ import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
3
+ import { s as shouldOpenSubscription, r as runOutsideAngular } from './platform-Dg8Bppgq.mjs';
4
+
5
+ const randomSessionId = (prefix = "sess") => {
6
+ if (typeof crypto !== "undefined") {
7
+ if (typeof crypto.randomUUID === "function") {
8
+ return crypto.randomUUID();
9
+ }
10
+ if (typeof crypto.getRandomValues === "function") {
11
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
12
+ return `${prefix}-${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
13
+ }
14
+ }
15
+ return `${prefix}-${Date.now().toString(36)}`;
16
+ };
17
+
18
+ const DEFAULT_INTERVAL_MS = 1e4;
19
+ const presence = (roomId, options) => {
20
+ const client = resolveLunoraClient(options.client);
21
+ const fromInjectionContext = options.destroyRef === void 0;
22
+ const destroyRef = options.destroyRef ?? inject(DestroyRef);
23
+ const { heartbeat, listPresent, shardKey } = options;
24
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
25
+ const sessionId = options.sessionId ?? randomSessionId();
26
+ const present = signal(void 0);
27
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
28
+ throw new RangeError(`presence intervalMs must be a positive number, got ${String(intervalMs)}`);
29
+ }
30
+ let latestData = options.data;
31
+ const sendHeartbeat = () => {
32
+ const args = { roomId, sessionId };
33
+ if (latestData !== void 0) {
34
+ args.data = latestData;
35
+ }
36
+ client.mutation(heartbeat, args, { shardKey }).catch(() => void 0);
37
+ };
38
+ const setData = (next) => {
39
+ latestData = next;
40
+ sendHeartbeat();
41
+ };
42
+ if (shouldOpenSubscription(fromInjectionContext)) {
43
+ const releaseConnectionContext = client.acquireConnectionContext({ roomId, sessionId }, { shardKey });
44
+ sendHeartbeat();
45
+ const onVisible = () => {
46
+ if (typeof document !== "undefined" && document.visibilityState === "visible") {
47
+ sendHeartbeat();
48
+ }
49
+ };
50
+ const intervalHandle = runOutsideAngular(fromInjectionContext, () => {
51
+ if (typeof document !== "undefined") {
52
+ document.addEventListener("visibilitychange", onVisible);
53
+ }
54
+ return setInterval(sendHeartbeat, intervalMs);
55
+ });
56
+ const listArgs = { roomId };
57
+ const unsubscribe = client.subscribe(
58
+ listPresent,
59
+ listArgs,
60
+ (value) => {
61
+ present.set(value);
62
+ },
63
+ { shardKey }
64
+ );
65
+ destroyRef.onDestroy(() => {
66
+ clearInterval(intervalHandle);
67
+ if (typeof document !== "undefined") {
68
+ document.removeEventListener("visibilitychange", onVisible);
69
+ }
70
+ releaseConnectionContext();
71
+ unsubscribe();
72
+ });
73
+ }
74
+ return { present: present.asReadonly(), sessionId, setData };
75
+ };
76
+
77
+ export { presence };
@@ -6,11 +6,9 @@ const rateLimit = (config, options = {}) => {
6
6
  const now = options.now ?? Date.now;
7
7
  const tickMs = options.tickMs ?? 1e3;
8
8
  let value;
9
- const epoch = signal(0);
10
9
  const computeStatus = () => evaluate(config, value, { consume: false, count: 1, now: now(), reserve: false }).status;
11
10
  const status = signal(computeStatus());
12
11
  const bump = () => {
13
- epoch.update((v) => v + 1);
14
12
  status.set(computeStatus());
15
13
  };
16
14
  let intervalHandle;
@@ -1,13 +1,15 @@
1
1
  import { inject, DestroyRef, signal } from '@angular/core';
2
2
  import { createQuerySubscription } from '@lunora/client/query';
3
3
  import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
4
+ import { s as shouldOpenSubscription } from './platform-Dg8Bppgq.mjs';
4
5
 
5
6
  const subscription = (reference, args, options = {}) => {
6
7
  const client = resolveLunoraClient(options.client);
8
+ const fromInjectionContext = options.destroyRef === void 0;
7
9
  const destroyRef = options.destroyRef ?? inject(DestroyRef);
8
10
  const data = signal(void 0);
9
11
  const error = signal(void 0);
10
- if (args !== "skip") {
12
+ if (args !== "skip" && shouldOpenSubscription(fromInjectionContext)) {
11
13
  const userOnError = options.onError;
12
14
  const unsubscribe = createQuerySubscription(
13
15
  client,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/angular",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Angular reactive adapter for Lunora — signal-based live queries and mutations",
5
5
  "keywords": [
6
6
  "angular",
@@ -45,8 +45,8 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@lunora/client": "1.0.0-alpha.19",
49
- "@lunora/ratelimit": "1.0.0-alpha.5"
48
+ "@lunora/client": "1.0.0-alpha.21",
49
+ "@lunora/ratelimit": "1.0.0-alpha.7"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@angular/core": "^19.2.0 || ^20.0.0 || ^21.0.0 || ^22.0.0"
@@ -1,28 +0,0 @@
1
- import { inject, DestroyRef, signal } from '@angular/core';
2
- import { createQuerySubscription } from '@lunora/client/query';
3
- import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
4
-
5
- const liveQuery = (reference, args, options = {}) => {
6
- const client = resolveLunoraClient(options.client);
7
- const destroyRef = options.destroyRef ?? inject(DestroyRef);
8
- const value = signal(void 0);
9
- const unsubscribe = createQuerySubscription(
10
- client,
11
- reference,
12
- args,
13
- {
14
- onData: (next) => {
15
- value.set(next);
16
- },
17
- onError: options.onError,
18
- onReset: () => {
19
- value.set(void 0);
20
- }
21
- },
22
- { shardKey: options.shardKey }
23
- );
24
- destroyRef.onDestroy(unsubscribe);
25
- return value.asReadonly();
26
- };
27
-
28
- export { liveQuery };
@@ -1,59 +0,0 @@
1
- import { inject, DestroyRef, signal } from '@angular/core';
2
- import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
3
-
4
- const makeSessionId = () => crypto.randomUUID();
5
- const DEFAULT_INTERVAL_MS = 1e4;
6
- const presence = (roomId, options) => {
7
- const client = resolveLunoraClient(options.client);
8
- const destroyRef = options.destroyRef ?? inject(DestroyRef);
9
- const { heartbeat, listPresent, shardKey } = options;
10
- const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
11
- const sessionId = options.sessionId ?? makeSessionId();
12
- const present = signal(void 0);
13
- if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
14
- throw new RangeError(`presence intervalMs must be a positive number, got ${String(intervalMs)}`);
15
- }
16
- let latestData = options.data;
17
- const releaseConnectionContext = client.acquireConnectionContext({ roomId, sessionId }, { shardKey });
18
- const sendHeartbeat = () => {
19
- const args = { roomId, sessionId };
20
- if (latestData !== void 0) {
21
- args.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 listArgs = { roomId };
40
- const unsubscribe = client.subscribe(
41
- listPresent,
42
- listArgs,
43
- (value) => {
44
- present.set(value);
45
- },
46
- { shardKey }
47
- );
48
- destroyRef.onDestroy(() => {
49
- clearInterval(intervalHandle);
50
- if (typeof document !== "undefined") {
51
- document.removeEventListener("visibilitychange", onVisible);
52
- }
53
- releaseConnectionContext();
54
- unsubscribe();
55
- });
56
- return { present: present.asReadonly(), sessionId, setData };
57
- };
58
-
59
- export { presence };