@lunora/angular 0.0.1 → 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.
@@ -0,0 +1,146 @@
1
+ import { InjectionToken, EnvironmentProviders, DestroyRef, Signal } from '@angular/core';
2
+ import { LunoraClient, LunoraClientOptions, ConnectionStatus, SubscriptionError, FunctionReference, ArgsOf, ReturnOf, MutationCallOptions } from '@lunora/client';
3
+ export type { ArgsOf, ConnectionStatus, FunctionReference, LunoraClient, LunoraClientOptions, MutationCallOptions, ReturnOf, SubscriptionError, Unsubscribe } from '@lunora/client';
4
+ export { SKIP } from '@lunora/client/query';
5
+ /**
6
+ * DI token carrying the framework-neutral {@link LunoraClient}. Every reactive
7
+ * primitive in this adapter (`liveQuery`, `mutate`, `connectionStatus`) reads the
8
+ * client from here, so a single {@link provideLunora} in the application config
9
+ * wires the whole app.
10
+ *
11
+ * The token has a root-scoped default factory, so it resolves even without
12
+ * {@link provideLunora}: it builds one same-origin browser client (which opens
13
+ * its WebSocket lazily on the first subscription). Call {@link provideLunora} to
14
+ * point it at a remote URL or hand it a pre-built client.
15
+ */
16
+ declare const LUNORA_CLIENT: InjectionToken<LunoraClient>;
17
+ /**
18
+ * Options accepted by {@link provideLunora}. Identical to {@link LunoraClientOptions}
19
+ * except `url` is optional — it defaults to the page origin in the browser (and to
20
+ * `""` on the server; pass an explicit `url` for SSR data-loading — see
21
+ * {@link sameOriginUrl}).
22
+ */
23
+ type ProvideLunoraOptions = Omit<LunoraClientOptions, "url"> & {
24
+ url?: string;
25
+ };
26
+ /**
27
+ * Wire a {@link LunoraClient} into the application injector. Add the result to the
28
+ * `providers` array of an Angular application config (or any `EnvironmentProviders`
29
+ * consumer):
30
+ *
31
+ * ```ts
32
+ * export const appConfig: ApplicationConfig = {
33
+ * providers: [provideLunora({ url: "https://api.example.com" })],
34
+ * };
35
+ * ```
36
+ *
37
+ * Pass {@link LunoraClientOptions} to configure a fresh client (URL defaults to
38
+ * the page origin), or hand in an already-constructed {@link LunoraClient} to
39
+ * share one instance (e.g. a client you also preload against during SSR).
40
+ */
41
+ declare const provideLunora: (optionsOrClient?: LunoraClient | ProvideLunoraOptions) => EnvironmentProviders;
42
+ /**
43
+ * Read the {@link LunoraClient} from the current injector. Call inside an
44
+ * injection context (a component/service field initializer or constructor, or a
45
+ * `runInInjectionContext` callback). Use it to hold the client for imperative
46
+ * calls — e.g. `mutation`/`action` from event handlers, which run outside an
47
+ * injection context:
48
+ *
49
+ * ```ts
50
+ * private readonly client = injectLunoraClient();
51
+ * send = (text: string) => this.client.mutation(api.messages.send, { text });
52
+ * ```
53
+ */
54
+ declare const injectLunoraClient: () => LunoraClient;
55
+ interface ConnectionStatusOptions {
56
+ /** Client to observe. Defaults to the injected `LUNORA_CLIENT`. */
57
+ client?: LunoraClient;
58
+ /** `DestroyRef` whose `onDestroy` removes the status listener. Defaults to `inject(DestroyRef)`. */
59
+ destroyRef?: DestroyRef;
60
+ }
61
+ /**
62
+ * A `signal` of the client's aggregate live-socket status across all shard
63
+ * connections. Reads the current status synchronously and updates on every
64
+ * transition (`idle` → `connecting` → `connected` → `offline`). The Angular
65
+ * equivalent of `@lunora/react`'s `useConnectionStatus`.
66
+ *
67
+ * The listener is removed when the owning `DestroyRef` fires. Call from an
68
+ * injection context (component/service field or constructor).
69
+ */
70
+ declare const connectionStatus: (options?: ConnectionStatusOptions) => Signal<ConnectionStatus>;
71
+ interface LiveQueryOptions {
72
+ /**
73
+ * Client to bind to. Defaults to the injected `LUNORA_CLIENT`; pass one
74
+ * explicitly to use `liveQuery` outside an injection context (or in a test).
75
+ */
76
+ client?: LunoraClient;
77
+ /**
78
+ * `DestroyRef` whose `onDestroy` tears the subscription down. Defaults to
79
+ * `inject(DestroyRef)` — the calling component/service — so it closes when that
80
+ * component is destroyed. Pass one explicitly to control the lifetime yourself.
81
+ */
82
+ destroyRef?: DestroyRef;
83
+ /**
84
+ * Called when the subscription errors after the initial attach — the async
85
+ * error channel `createQuerySubscription` only wires when a sink is present.
86
+ * Without it, a post-attach failure is dropped silently: the signal simply
87
+ * stops updating with no error state exposed. Pass a handler to surface it
88
+ * (log, toast, set an error signal of your own).
89
+ */
90
+ onError?: (error: SubscriptionError) => void;
91
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
92
+ shardKey?: string;
93
+ }
94
+ /**
95
+ * Subscribe to a server query and mirror its value into an Angular `signal`.
96
+ *
97
+ * Reads `undefined` until the first server frame lands, then updates on every
98
+ * delta the WebSocket pushes. The underlying subscription is torn down
99
+ * automatically when the owning `DestroyRef` fires (the component/service is
100
+ * destroyed), so there is no leaked socket subscription.
101
+ *
102
+ * Call it from an injection context (a component/service field initializer or
103
+ * constructor) so the default `inject(DestroyRef)` resolves the caller's
104
+ * lifetime:
105
+ *
106
+ * ```ts
107
+ * export class MessagesComponent {
108
+ * readonly messages = liveQuery(api.messages.list, { channelId: "general" });
109
+ * }
110
+ * ```
111
+ *
112
+ * Pass `"skip"` (the `SKIP` sentinel from `@lunora/client/query`) as `args` to
113
+ * short-circuit — no network call, no socket; the signal stays `undefined`. To
114
+ * call outside an injection context (e.g. lazily in `ngOnInit`), supply `client`
115
+ * and `destroyRef` via {@link LiveQueryOptions}.
116
+ */
117
+ declare const liveQuery: <F extends FunctionReference>(reference: F, args: ArgsOf<F> | "skip", options?: LiveQueryOptions) => Signal<ReturnOf<F> | undefined>;
118
+ interface MutateOptions<F extends FunctionReference> extends MutationCallOptions<unknown, unknown, ArgsOf<F>> {
119
+ /**
120
+ * Client to run the mutation on. Defaults to the injected `LUNORA_CLIENT`.
121
+ * Because mutations usually fire from event handlers — which run *outside* an
122
+ * injection context — capture the client once (`injectLunoraClient()` in a
123
+ * field) and pass it here, or call `client.mutation(...)` directly.
124
+ */
125
+ client?: LunoraClient;
126
+ }
127
+ /**
128
+ * Run a Lunora mutation and resolve with the server result (rejects on failure).
129
+ *
130
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
131
+ * call options pass straight through to `client.mutation`, which applies and
132
+ * rolls them back against the live subscription cache — the same cache
133
+ * `liveQuery` reads, so an optimistic write reflects immediately and
134
+ * reverts on failure. The client's offline queue also engages when the socket is
135
+ * down, so the write stays durable across reconnects.
136
+ *
137
+ * ```ts
138
+ * private readonly client = injectLunoraClient();
139
+ * send = (text: string) => mutate(api.messages.send, { text }, { client: this.client });
140
+ * ```
141
+ *
142
+ * When called from within an injection context you may omit `client` and let it
143
+ * resolve from the injector.
144
+ */
145
+ declare const mutate: <F extends FunctionReference>(reference: F, args: ArgsOf<F>, options?: MutateOptions<F>) => Promise<ReturnOf<F>>;
146
+ export { type ConnectionStatusOptions, LUNORA_CLIENT, type LiveQueryOptions, type MutateOptions, type ProvideLunoraOptions, connectionStatus, injectLunoraClient, liveQuery, mutate, provideLunora };
@@ -0,0 +1,146 @@
1
+ import { InjectionToken, EnvironmentProviders, DestroyRef, Signal } from '@angular/core';
2
+ import { LunoraClient, LunoraClientOptions, ConnectionStatus, SubscriptionError, FunctionReference, ArgsOf, ReturnOf, MutationCallOptions } from '@lunora/client';
3
+ export type { ArgsOf, ConnectionStatus, FunctionReference, LunoraClient, LunoraClientOptions, MutationCallOptions, ReturnOf, SubscriptionError, Unsubscribe } from '@lunora/client';
4
+ export { SKIP } from '@lunora/client/query';
5
+ /**
6
+ * DI token carrying the framework-neutral {@link LunoraClient}. Every reactive
7
+ * primitive in this adapter (`liveQuery`, `mutate`, `connectionStatus`) reads the
8
+ * client from here, so a single {@link provideLunora} in the application config
9
+ * wires the whole app.
10
+ *
11
+ * The token has a root-scoped default factory, so it resolves even without
12
+ * {@link provideLunora}: it builds one same-origin browser client (which opens
13
+ * its WebSocket lazily on the first subscription). Call {@link provideLunora} to
14
+ * point it at a remote URL or hand it a pre-built client.
15
+ */
16
+ declare const LUNORA_CLIENT: InjectionToken<LunoraClient>;
17
+ /**
18
+ * Options accepted by {@link provideLunora}. Identical to {@link LunoraClientOptions}
19
+ * except `url` is optional — it defaults to the page origin in the browser (and to
20
+ * `""` on the server; pass an explicit `url` for SSR data-loading — see
21
+ * {@link sameOriginUrl}).
22
+ */
23
+ type ProvideLunoraOptions = Omit<LunoraClientOptions, "url"> & {
24
+ url?: string;
25
+ };
26
+ /**
27
+ * Wire a {@link LunoraClient} into the application injector. Add the result to the
28
+ * `providers` array of an Angular application config (or any `EnvironmentProviders`
29
+ * consumer):
30
+ *
31
+ * ```ts
32
+ * export const appConfig: ApplicationConfig = {
33
+ * providers: [provideLunora({ url: "https://api.example.com" })],
34
+ * };
35
+ * ```
36
+ *
37
+ * Pass {@link LunoraClientOptions} to configure a fresh client (URL defaults to
38
+ * the page origin), or hand in an already-constructed {@link LunoraClient} to
39
+ * share one instance (e.g. a client you also preload against during SSR).
40
+ */
41
+ declare const provideLunora: (optionsOrClient?: LunoraClient | ProvideLunoraOptions) => EnvironmentProviders;
42
+ /**
43
+ * Read the {@link LunoraClient} from the current injector. Call inside an
44
+ * injection context (a component/service field initializer or constructor, or a
45
+ * `runInInjectionContext` callback). Use it to hold the client for imperative
46
+ * calls — e.g. `mutation`/`action` from event handlers, which run outside an
47
+ * injection context:
48
+ *
49
+ * ```ts
50
+ * private readonly client = injectLunoraClient();
51
+ * send = (text: string) => this.client.mutation(api.messages.send, { text });
52
+ * ```
53
+ */
54
+ declare const injectLunoraClient: () => LunoraClient;
55
+ interface ConnectionStatusOptions {
56
+ /** Client to observe. Defaults to the injected `LUNORA_CLIENT`. */
57
+ client?: LunoraClient;
58
+ /** `DestroyRef` whose `onDestroy` removes the status listener. Defaults to `inject(DestroyRef)`. */
59
+ destroyRef?: DestroyRef;
60
+ }
61
+ /**
62
+ * A `signal` of the client's aggregate live-socket status across all shard
63
+ * connections. Reads the current status synchronously and updates on every
64
+ * transition (`idle` → `connecting` → `connected` → `offline`). The Angular
65
+ * equivalent of `@lunora/react`'s `useConnectionStatus`.
66
+ *
67
+ * The listener is removed when the owning `DestroyRef` fires. Call from an
68
+ * injection context (component/service field or constructor).
69
+ */
70
+ declare const connectionStatus: (options?: ConnectionStatusOptions) => Signal<ConnectionStatus>;
71
+ interface LiveQueryOptions {
72
+ /**
73
+ * Client to bind to. Defaults to the injected `LUNORA_CLIENT`; pass one
74
+ * explicitly to use `liveQuery` outside an injection context (or in a test).
75
+ */
76
+ client?: LunoraClient;
77
+ /**
78
+ * `DestroyRef` whose `onDestroy` tears the subscription down. Defaults to
79
+ * `inject(DestroyRef)` — the calling component/service — so it closes when that
80
+ * component is destroyed. Pass one explicitly to control the lifetime yourself.
81
+ */
82
+ destroyRef?: DestroyRef;
83
+ /**
84
+ * Called when the subscription errors after the initial attach — the async
85
+ * error channel `createQuerySubscription` only wires when a sink is present.
86
+ * Without it, a post-attach failure is dropped silently: the signal simply
87
+ * stops updating with no error state exposed. Pass a handler to surface it
88
+ * (log, toast, set an error signal of your own).
89
+ */
90
+ onError?: (error: SubscriptionError) => void;
91
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
92
+ shardKey?: string;
93
+ }
94
+ /**
95
+ * Subscribe to a server query and mirror its value into an Angular `signal`.
96
+ *
97
+ * Reads `undefined` until the first server frame lands, then updates on every
98
+ * delta the WebSocket pushes. The underlying subscription is torn down
99
+ * automatically when the owning `DestroyRef` fires (the component/service is
100
+ * destroyed), so there is no leaked socket subscription.
101
+ *
102
+ * Call it from an injection context (a component/service field initializer or
103
+ * constructor) so the default `inject(DestroyRef)` resolves the caller's
104
+ * lifetime:
105
+ *
106
+ * ```ts
107
+ * export class MessagesComponent {
108
+ * readonly messages = liveQuery(api.messages.list, { channelId: "general" });
109
+ * }
110
+ * ```
111
+ *
112
+ * Pass `"skip"` (the `SKIP` sentinel from `@lunora/client/query`) as `args` to
113
+ * short-circuit — no network call, no socket; the signal stays `undefined`. To
114
+ * call outside an injection context (e.g. lazily in `ngOnInit`), supply `client`
115
+ * and `destroyRef` via {@link LiveQueryOptions}.
116
+ */
117
+ declare const liveQuery: <F extends FunctionReference>(reference: F, args: ArgsOf<F> | "skip", options?: LiveQueryOptions) => Signal<ReturnOf<F> | undefined>;
118
+ interface MutateOptions<F extends FunctionReference> extends MutationCallOptions<unknown, unknown, ArgsOf<F>> {
119
+ /**
120
+ * Client to run the mutation on. Defaults to the injected `LUNORA_CLIENT`.
121
+ * Because mutations usually fire from event handlers — which run *outside* an
122
+ * injection context — capture the client once (`injectLunoraClient()` in a
123
+ * field) and pass it here, or call `client.mutation(...)` directly.
124
+ */
125
+ client?: LunoraClient;
126
+ }
127
+ /**
128
+ * Run a Lunora mutation and resolve with the server result (rejects on failure).
129
+ *
130
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
131
+ * call options pass straight through to `client.mutation`, which applies and
132
+ * rolls them back against the live subscription cache — the same cache
133
+ * `liveQuery` reads, so an optimistic write reflects immediately and
134
+ * reverts on failure. The client's offline queue also engages when the socket is
135
+ * down, so the write stays durable across reconnects.
136
+ *
137
+ * ```ts
138
+ * private readonly client = injectLunoraClient();
139
+ * send = (text: string) => mutate(api.messages.send, { text }, { client: this.client });
140
+ * ```
141
+ *
142
+ * When called from within an injection context you may omit `client` and let it
143
+ * resolve from the injector.
144
+ */
145
+ declare const mutate: <F extends FunctionReference>(reference: F, args: ArgsOf<F>, options?: MutateOptions<F>) => Promise<ReturnOf<F>>;
146
+ export { type ConnectionStatusOptions, LUNORA_CLIENT, type LiveQueryOptions, type MutateOptions, type ProvideLunoraOptions, connectionStatus, injectLunoraClient, liveQuery, mutate, provideLunora };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ export { LUNORA_CLIENT, injectLunoraClient, provideLunora } from './packem_shared/LUNORA_CLIENT-DHUfNu9x.mjs';
2
+ export { connectionStatus } from './packem_shared/connectionStatus-BlLodleK.mjs';
3
+ export { liveQuery } from './packem_shared/liveQuery-D5VQBOgf.mjs';
4
+ export { mutate } from './packem_shared/mutate-D3rEHwbb.mjs';
5
+ export { SKIP } from '@lunora/client/query';
@@ -0,0 +1,23 @@
1
+ import { InjectionToken, makeEnvironmentProviders, inject } from '@angular/core';
2
+ import { LunoraClient } from '@lunora/client';
3
+
4
+ const sameOriginUrl = () => globalThis.location?.origin ?? "";
5
+ const LUNORA_CLIENT = new InjectionToken("lunora.client", {
6
+ factory: () => new LunoraClient({ url: sameOriginUrl() }),
7
+ providedIn: "root"
8
+ });
9
+ const provideLunora = (optionsOrClient = {}) => makeEnvironmentProviders([
10
+ {
11
+ provide: LUNORA_CLIENT,
12
+ useFactory: () => {
13
+ if (optionsOrClient instanceof LunoraClient) {
14
+ return optionsOrClient;
15
+ }
16
+ return new LunoraClient({ ...optionsOrClient, url: optionsOrClient.url ?? sameOriginUrl() });
17
+ }
18
+ }
19
+ ]);
20
+ const injectLunoraClient = () => inject(LUNORA_CLIENT);
21
+ const resolveLunoraClient = (client) => client ?? injectLunoraClient();
22
+
23
+ export { LUNORA_CLIENT, injectLunoraClient, provideLunora, resolveLunoraClient };
@@ -0,0 +1,15 @@
1
+ import { inject, DestroyRef, signal } from '@angular/core';
2
+ import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
3
+
4
+ const connectionStatus = (options = {}) => {
5
+ const client = resolveLunoraClient(options.client);
6
+ const destroyRef = options.destroyRef ?? inject(DestroyRef);
7
+ const status = signal(client.connectionStatus());
8
+ const unsubscribe = client.onConnectionStatus((next) => {
9
+ status.set(next);
10
+ });
11
+ destroyRef.onDestroy(unsubscribe);
12
+ return status.asReadonly();
13
+ };
14
+
15
+ export { connectionStatus };
@@ -0,0 +1,28 @@
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 };
@@ -0,0 +1,8 @@
1
+ import { resolveLunoraClient } from './LUNORA_CLIENT-DHUfNu9x.mjs';
2
+
3
+ const mutate = (reference, args, options = {}) => {
4
+ const { client, ...callOptions } = options;
5
+ return resolveLunoraClient(client).mutation(reference, args, callOptions);
6
+ };
7
+
8
+ export { mutate };
package/package.json CHANGED
@@ -1,10 +1,61 @@
1
1
  {
2
2
  "name": "@lunora/angular",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @lunora/angular",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "Angular reactive adapter for Lunora signal-based live queries and mutations",
5
5
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
6
+ "angular",
7
+ "cloudflare",
8
+ "durable-objects",
9
+ "lunora",
10
+ "realtime",
11
+ "signals",
12
+ "workers"
13
+ ],
14
+ "homepage": "https://lunora.sh",
15
+ "bugs": "https://github.com/anolilab/lunora/issues",
16
+ "license": "FSL-1.1-Apache-2.0",
17
+ "author": {
18
+ "name": "Daniel Bannert",
19
+ "email": "d.bannert@anolilab.de"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/anolilab/lunora.git",
24
+ "directory": "packages/angular"
25
+ },
26
+ "files": [
27
+ "./dist",
28
+ "README.md",
29
+ "LICENSE.md",
30
+ "__assets__"
31
+ ],
32
+ "type": "module",
33
+ "sideEffects": false,
34
+ "main": "./dist/index.mjs",
35
+ "module": "./dist/index.mjs",
36
+ "types": "./dist/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.mjs"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@lunora/client": "1.0.0-alpha.18"
49
+ },
50
+ "peerDependencies": {
51
+ "@angular/core": "^19.2.0 || ^20.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@angular/core": {
55
+ "optional": false
56
+ }
57
+ },
58
+ "engines": {
59
+ "node": "^22.15.0 || >=24.11.0"
60
+ }
61
+ }