universal-api-layer 0.7.0

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,81 @@
1
+ import { f as ApiError, e as ApiClient, h as ResponseSchema } from './client-ByBlZaRg.js';
2
+
3
+ type SocketData = string | ArrayBuffer | Blob;
4
+ type SocketSendData = string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>;
5
+ interface SocketEvents {
6
+ open: {
7
+ readonly type: string;
8
+ };
9
+ error: {
10
+ readonly type: string;
11
+ };
12
+ message: {
13
+ readonly type: string;
14
+ readonly data: unknown;
15
+ };
16
+ close: {
17
+ readonly type: string;
18
+ readonly code: number;
19
+ readonly reason: string;
20
+ readonly wasClean: boolean;
21
+ };
22
+ }
23
+ /** Structural native WebSocket interface, also injectable in tests/non-browser runtimes. */
24
+ interface SocketTransport {
25
+ readonly readyState: number;
26
+ readonly bufferedAmount: number;
27
+ binaryType: "blob" | "arraybuffer";
28
+ send(data: SocketSendData): void;
29
+ close(code?: number, reason?: string): void;
30
+ addEventListener<K extends keyof SocketEvents>(type: K, listener: (event: SocketEvents[K]) => void): void;
31
+ removeEventListener<K extends keyof SocketEvents>(type: K, listener: (event: SocketEvents[K]) => void): void;
32
+ }
33
+ interface SocketSnapshot {
34
+ readonly status: "idle" | "connecting" | "open" | "reconnecting" | "closed" | "disposed";
35
+ /** Retries used since the latest explicit connect(), including across short-lived opens. */
36
+ readonly reconnectAttempt: number;
37
+ readonly error: ApiError | null;
38
+ readonly close: Readonly<{
39
+ code: number;
40
+ reason: string;
41
+ wasClean: boolean;
42
+ }> | null;
43
+ }
44
+ interface SocketOptions<T> {
45
+ /** Absolute ws(s) URL, or an abortable provider for short-lived connection tickets. */
46
+ url: string | ((signal: AbortSignal) => string | Promise<string>);
47
+ /** Required for a URL provider. Exact ws(s) origins, not URL prefixes. */
48
+ allowedOrigins?: readonly string[];
49
+ protocols?: readonly string[];
50
+ sessionClient?: Pick<ApiClient, "getSessionSnapshot" | "subscribeSession">;
51
+ /** Defaults to true when sessionClient is provided. */
52
+ requiresAuth?: boolean;
53
+ /** json (default) parses text JSON; raw preserves native string/ArrayBuffer/Blob data. */
54
+ format?: "json" | "raw";
55
+ schema?: ResponseSchema<T>;
56
+ reconnect?: false | {
57
+ maxAttempts?: number;
58
+ baseDelayMs?: number;
59
+ maxDelayMs?: number;
60
+ jitter?: boolean;
61
+ };
62
+ connectTimeoutMs?: number;
63
+ maxMessageSize?: number;
64
+ maxPendingMessages?: number;
65
+ maxBufferedAmount?: number;
66
+ webSocketFactory?: (url: string, protocols: string[]) => SocketTransport;
67
+ }
68
+ interface SocketClient<T = unknown> {
69
+ connect(): void;
70
+ disconnect(code?: number, reason?: string): void;
71
+ dispose(): void;
72
+ /** Sends only while OPEN and under the buffer limit; never queues or replays. */
73
+ send(data: SocketSendData): void;
74
+ sendJson(value: unknown): void;
75
+ getSnapshot(): SocketSnapshot;
76
+ subscribe(listener: () => void): () => void;
77
+ onMessage(listener: (message: T) => void): () => void;
78
+ onError(listener: (error: ApiError) => void): () => void;
79
+ }
80
+
81
+ export type { SocketClient as S, SocketSnapshot as a, SocketOptions as b, SocketData as c, SocketEvents as d, SocketSendData as e, SocketTransport as f };
@@ -0,0 +1,23 @@
1
+ import * as vue from 'vue';
2
+ import { MaybeRefOrGetter, Plugin } from 'vue';
3
+ import { QueryConfig, ApiClient, MutationConfig, ApiError, SessionSnapshot } from '../core/index.js';
4
+ import { QueryClient, UseMutationReturnType, UseQueryReturnType } from '@tanstack/vue-query';
5
+
6
+ declare function createApiPlugin({ client, queryClient, }: {
7
+ client: ApiClient;
8
+ queryClient?: QueryClient;
9
+ }): Plugin;
10
+ declare function useApiClient(): ApiClient;
11
+ declare function useApiSession(): vue.ComputedRef<SessionSnapshot>;
12
+ type VueQueryConfig<T, S = T | null> = Omit<QueryConfig<T, S>, "key" | "url" | "params" | "enabled" | "cacheScope"> & {
13
+ key: MaybeRefOrGetter<readonly unknown[]>;
14
+ url: MaybeRefOrGetter<string>;
15
+ params?: MaybeRefOrGetter<QueryConfig<T, S>["params"]>;
16
+ enabled?: MaybeRefOrGetter<boolean>;
17
+ cacheScope?: MaybeRefOrGetter<string>;
18
+ };
19
+ declare function useApiQuery<T = unknown, S = T | null>(config: VueQueryConfig<T, S>): UseQueryReturnType<S, ApiError>;
20
+ declare function useApiMutation<T = unknown, V = unknown, S = T | null>(config: MutationConfig<T, V, S>): UseMutationReturnType<S, ApiError, V, unknown>;
21
+
22
+ export { createApiPlugin, useApiClient, useApiMutation, useApiQuery, useApiSession };
23
+ export type { VueQueryConfig };
@@ -0,0 +1,257 @@
1
+ import {
2
+ defaultQueryRetry,
3
+ queryIdentity,
4
+ runMutation
5
+ } from "../chunk-HDW7BOZI.js";
6
+ import {
7
+ invokeAsyncCallback,
8
+ invokeCallback
9
+ } from "../chunk-DXYWZQW5.js";
10
+ import "../chunk-YTM3FV65.js";
11
+
12
+ // src/vue/index.ts
13
+ import {
14
+ computed,
15
+ inject,
16
+ onScopeDispose,
17
+ shallowRef,
18
+ toValue
19
+ } from "vue";
20
+ import {
21
+ QueryClient,
22
+ VueQueryPlugin,
23
+ useQuery,
24
+ useMutation,
25
+ useQueryClient
26
+ } from "@tanstack/vue-query";
27
+ var clientKey = /* @__PURE__ */ Symbol("universal-api-layer");
28
+ function clearClientCache(client, cache, staleOnly = false) {
29
+ const current = client.getSessionSnapshot();
30
+ const predicate = (query) => {
31
+ if (!client.ownsQuery(query.queryKey)) return false;
32
+ if (!staleOnly) return true;
33
+ const snapshot = query.queryKey.at(-1).session;
34
+ return !snapshot || snapshot.revision !== current.revision || snapshot.userId !== current.userId || snapshot.tenantId !== current.tenantId;
35
+ };
36
+ void cache.cancelQueries({ predicate }).catch(() => {
37
+ });
38
+ cache.removeQueries({ predicate });
39
+ for (const mutation of cache.getMutationCache().getAll()) {
40
+ const variables = mutation.state.variables;
41
+ const revision = variables && typeof variables === "object" && "revision" in variables ? variables.revision : void 0;
42
+ if (mutation.options.meta?.universalApiLayer === client.cacheScope && (!staleOnly || revision !== current.revision))
43
+ cache.getMutationCache().remove(mutation);
44
+ }
45
+ }
46
+ var cacheBindings = /* @__PURE__ */ new WeakMap();
47
+ function bindCache(client, cache) {
48
+ let caches = cacheBindings.get(client);
49
+ if (!caches) {
50
+ caches = /* @__PURE__ */ new WeakMap();
51
+ cacheBindings.set(client, caches);
52
+ }
53
+ let binding = caches.get(cache);
54
+ if (!binding) {
55
+ clearClientCache(client, cache, true);
56
+ binding = {
57
+ count: 0,
58
+ unsubscribe: client.subscribeSession(
59
+ () => clearClientCache(client, cache)
60
+ )
61
+ };
62
+ caches.set(cache, binding);
63
+ }
64
+ binding.count++;
65
+ const current = binding;
66
+ return () => {
67
+ current.count--;
68
+ if (current.count === 0) {
69
+ current.unsubscribe();
70
+ clearClientCache(client, cache);
71
+ caches.delete(cache);
72
+ }
73
+ };
74
+ }
75
+ function createApiPlugin({
76
+ client,
77
+ queryClient
78
+ }) {
79
+ return {
80
+ install(app) {
81
+ const cache = queryClient ?? new QueryClient();
82
+ app.provide(clientKey, client);
83
+ app.use(VueQueryPlugin, { queryClient: cache });
84
+ const unsubscribe = bindCache(client, cache);
85
+ const unmount = app.unmount.bind(app);
86
+ app.unmount = () => {
87
+ unsubscribe();
88
+ unmount();
89
+ };
90
+ }
91
+ };
92
+ }
93
+ function useApiClient() {
94
+ const client = inject(clientKey);
95
+ if (!client)
96
+ throw new Error("Install createApiPlugin before using API composables");
97
+ return client;
98
+ }
99
+ function useSession(client) {
100
+ const snapshot = shallowRef(client.getSessionSnapshot());
101
+ const unsubscribe = client.subscribeSession(() => {
102
+ snapshot.value = client.getSessionSnapshot();
103
+ });
104
+ onScopeDispose(unsubscribe);
105
+ return computed(() => snapshot.value);
106
+ }
107
+ function useApiSession() {
108
+ return useSession(useApiClient());
109
+ }
110
+ function useApiQuery(config) {
111
+ const client = useApiClient();
112
+ const session = useSession(client);
113
+ const cache = useQueryClient();
114
+ return useQuery(
115
+ computed(() => {
116
+ const url = toValue(config.url), params = toValue(config.params);
117
+ const snapshot = session.value;
118
+ return {
119
+ queryKey: queryIdentity(
120
+ client,
121
+ toValue(config.key),
122
+ url,
123
+ params,
124
+ config.headers,
125
+ toValue(config.cacheScope)
126
+ ),
127
+ queryFn: ({ signal }) => {
128
+ client.assertSession(snapshot.revision);
129
+ return client.request(url, {
130
+ params,
131
+ headers: config.headers,
132
+ timeoutMs: config.timeoutMs,
133
+ signal,
134
+ schema: config.schema,
135
+ authRetry: config.authRetry,
136
+ requiresAuth: config.requiresAuth
137
+ });
138
+ },
139
+ select: config.select ? (data) => invokeCallback(() => config.select(data)) : void 0,
140
+ enabled: config.requiresAuth ? !!snapshot.userId && toValue(config.enabled) !== false : toValue(config.enabled),
141
+ staleTime: config.staleTime,
142
+ retry: config.retry ?? cache.getDefaultOptions().queries?.retry ?? defaultQueryRetry
143
+ };
144
+ })
145
+ );
146
+ }
147
+ function useApiMutation(config) {
148
+ const client = useApiClient(), cache = useQueryClient(), session = useSession(client);
149
+ const mutation = useMutation(
150
+ computed(() => ({
151
+ mutationKey: [
152
+ "universal-api-layer",
153
+ client.cacheScope,
154
+ session.value.revision
155
+ ],
156
+ meta: { universalApiLayer: client.cacheScope },
157
+ mutationFn: async (envelope) => {
158
+ client.assertSession(envelope.revision);
159
+ const data = await runMutation(client, config, envelope.input);
160
+ client.assertSession(envelope.revision);
161
+ return data;
162
+ },
163
+ onSuccess: async (data, envelope) => {
164
+ client.assertSession(envelope.revision);
165
+ await Promise.all(
166
+ (config.invalidateKeys ?? []).map(
167
+ (queryKey) => cache.invalidateQueries({
168
+ queryKey,
169
+ predicate: config.invalidateScope === "all" ? void 0 : (query) => client.ownsQuery(query.queryKey)
170
+ })
171
+ )
172
+ );
173
+ client.assertSession(envelope.revision);
174
+ await invokeAsyncCallback(
175
+ () => config.onSuccess?.(data, envelope.input)
176
+ );
177
+ client.assertSession(envelope.revision);
178
+ }
179
+ }))
180
+ );
181
+ const translate = (options) => options ? {
182
+ onSuccess: (data, envelope, context, mutationContext) => {
183
+ if (envelope.revision === client.getSessionSnapshot().revision)
184
+ options.onSuccess?.(
185
+ data,
186
+ envelope.input,
187
+ context,
188
+ mutationContext
189
+ );
190
+ },
191
+ onError: (error, envelope, context, mutationContext) => {
192
+ if (envelope.revision === client.getSessionSnapshot().revision)
193
+ options.onError?.(
194
+ error,
195
+ envelope.input,
196
+ context,
197
+ mutationContext
198
+ );
199
+ },
200
+ onSettled: (data, error, envelope, context, mutationContext) => {
201
+ if (envelope.revision === client.getSessionSnapshot().revision)
202
+ options.onSettled?.(
203
+ data,
204
+ error,
205
+ envelope.input,
206
+ context,
207
+ mutationContext
208
+ );
209
+ }
210
+ } : void 0;
211
+ const current = computed(
212
+ () => !mutation.variables.value || mutation.variables.value.revision === session.value.revision
213
+ );
214
+ return {
215
+ ...mutation,
216
+ data: computed(() => current.value ? mutation.data.value : void 0),
217
+ error: computed(() => current.value ? mutation.error.value : null),
218
+ variables: computed(
219
+ () => current.value ? mutation.variables.value?.input : void 0
220
+ ),
221
+ context: computed(
222
+ () => current.value ? mutation.context.value : void 0
223
+ ),
224
+ status: computed(() => current.value ? mutation.status.value : "idle"),
225
+ isPaused: computed(() => current.value && mutation.isPaused.value),
226
+ isIdle: computed(() => !current.value || mutation.isIdle.value),
227
+ isPending: computed(() => current.value && mutation.isPending.value),
228
+ isSuccess: computed(() => current.value && mutation.isSuccess.value),
229
+ isError: computed(() => current.value && mutation.isError.value),
230
+ submittedAt: computed(
231
+ () => current.value ? mutation.submittedAt.value : 0
232
+ ),
233
+ failureCount: computed(
234
+ () => current.value ? mutation.failureCount.value : 0
235
+ ),
236
+ failureReason: computed(
237
+ () => current.value ? mutation.failureReason.value : null
238
+ ),
239
+ mutate: (input, options) => mutation.mutate(
240
+ { input, revision: client.getSessionSnapshot().revision },
241
+ translate(options)
242
+ ),
243
+ mutateAsync: (input, options) => client.withSession(
244
+ () => mutation.mutateAsync(
245
+ { input, revision: client.getSessionSnapshot().revision },
246
+ translate(options)
247
+ )
248
+ )
249
+ };
250
+ }
251
+ export {
252
+ createApiPlugin,
253
+ useApiClient,
254
+ useApiMutation,
255
+ useApiQuery,
256
+ useApiSession
257
+ };
@@ -0,0 +1,9 @@
1
+ import { Signal } from '@angular/core';
2
+ import { SocketClient, SocketSnapshot } from './index.js';
3
+ import '../client-ByBlZaRg.js';
4
+
5
+ /** Subscription only: the application owns connect/disconnect/dispose. */
6
+ declare function injectSocketState<T>(client: SocketClient<T>): Signal<SocketSnapshot>;
7
+ declare function injectSocketMessage<T>(client: SocketClient<T>, listener: (message: T) => void): void;
8
+
9
+ export { injectSocketMessage, injectSocketState };
@@ -0,0 +1,15 @@
1
+ // src/websocket/angular.ts
2
+ import { DestroyRef, inject, signal } from "@angular/core";
3
+ function injectSocketState(client) {
4
+ const destroy = inject(DestroyRef);
5
+ const state = signal(client.getSnapshot());
6
+ destroy.onDestroy(client.subscribe(() => state.set(client.getSnapshot())));
7
+ return state.asReadonly();
8
+ }
9
+ function injectSocketMessage(client, listener) {
10
+ inject(DestroyRef).onDestroy(client.onMessage(listener));
11
+ }
12
+ export {
13
+ injectSocketMessage,
14
+ injectSocketState
15
+ };
@@ -0,0 +1,7 @@
1
+ import { b as SocketOptions, S as SocketClient } from '../types-CVzD7l4p.js';
2
+ export { c as SocketData, d as SocketEvents, e as SocketSendData, a as SocketSnapshot, f as SocketTransport } from '../types-CVzD7l4p.js';
3
+ import '../client-ByBlZaRg.js';
4
+
5
+ declare function createSocketClient<T = unknown>(config: SocketOptions<T>): SocketClient<T>;
6
+
7
+ export { SocketClient, SocketOptions, createSocketClient };