@vireocodedev/infrastructure 0.2.0 → 0.2.1
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/api/createModeAwareApi.d.ts +28 -0
- package/dist/api/transactional.d.ts +4 -0
- package/dist/errors/AppOfflineQueuedError.d.ts +3 -0
- package/dist/errors/OfflineModeNotSupportedError.d.ts +3 -0
- package/dist/http/AxiosHttpClient.d.ts +23 -0
- package/dist/http/axiosErrorReporting.d.ts +11 -0
- package/dist/http/isRequestCanceled.d.ts +1 -0
- package/dist/http/pagedSearch.d.ts +29 -0
- package/dist/http/pagination.d.ts +13 -0
- package/dist/http/pagination.js +0 -0
- package/dist/http/sessionExpiry.d.ts +23 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +400 -0
- package/dist/index.js.map +1 -0
- package/dist/network/appNetworkStatus.d.ts +6 -0
- package/dist/network/appNetworkStatus.js +21 -0
- package/dist/network/appNetworkStatus.js.map +1 -0
- package/dist/network/createConnectivityState.d.ts +31 -0
- package/dist/signals/createPersistentSignal.d.ts +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type ModeAwareApiMode = "offline" | "online";
|
|
2
|
+
export type ModeAwareApiInvocationContext = {
|
|
3
|
+
moduleKey: string;
|
|
4
|
+
methodName: string;
|
|
5
|
+
methodLabel: string;
|
|
6
|
+
online: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ModeAwareApiInvocationEvent = ModeAwareApiInvocationContext & {
|
|
9
|
+
mode: ModeAwareApiMode;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
};
|
|
12
|
+
export type CreateModeAwareApiOptions<TOnlineApi extends Record<string, unknown>, TOfflineApi extends Partial<{
|
|
13
|
+
[TModuleKey in keyof TOnlineApi]: unknown;
|
|
14
|
+
}>> = {
|
|
15
|
+
onlineApi: TOnlineApi;
|
|
16
|
+
offlineApi: TOfflineApi;
|
|
17
|
+
readOnline: () => boolean;
|
|
18
|
+
assertCanInvoke?: (context: ModeAwareApiInvocationContext) => void;
|
|
19
|
+
isOfflineTransactional?: (offlineMethod: unknown, context: ModeAwareApiInvocationContext) => boolean;
|
|
20
|
+
isOfflineFallbackError?: (error: unknown, context: ModeAwareApiInvocationContext) => boolean;
|
|
21
|
+
createNoFallbackError?: (context: ModeAwareApiInvocationContext) => Error;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
onInvokeSuccess?: (event: ModeAwareApiInvocationEvent) => void;
|
|
24
|
+
onInvokeError?: (event: ModeAwareApiInvocationEvent, error: unknown) => void;
|
|
25
|
+
};
|
|
26
|
+
export declare function createModeAwareApi<TOnlineApi extends Record<string, unknown>, TOfflineApi extends Partial<{
|
|
27
|
+
[TModuleKey in keyof TOnlineApi]: unknown;
|
|
28
|
+
}>>({ onlineApi, offlineApi, readOnline, assertCanInvoke, isOfflineTransactional, isOfflineFallbackError, createNoFallbackError, now, onInvokeSuccess, onInvokeError, }: CreateModeAwareApiOptions<TOnlineApi, TOfflineApi>): TOnlineApi;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
type AsyncMethod<TThis, TArgs extends unknown[], TResult> = (this: TThis, ...args: TArgs) => Promise<TResult>;
|
|
2
|
+
export declare function getTransactionalMetadata(value: unknown): boolean;
|
|
3
|
+
export declare function transactional<TThis, TArgs extends unknown[], TResult>(): (originalMethod: AsyncMethod<TThis, TArgs, TResult>, context: ClassMethodDecoratorContext<TThis, AsyncMethod<TThis, TArgs, TResult>>) => AsyncMethod<TThis, TArgs, TResult>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { default as z } from 'zod';
|
|
3
|
+
import { PageableParams, PageableResponse } from './pagination';
|
|
4
|
+
export type HttpEndpointResolver = (base: string, ...segments: (number | string)[]) => string;
|
|
5
|
+
export declare function resolveHttpEndpoint(base: string, ...segments: (number | string)[]): string;
|
|
6
|
+
export declare function parseHttpResponse<TSchema extends z.ZodTypeAny>(schema: TSchema, data: unknown): z.infer<TSchema>;
|
|
7
|
+
export declare abstract class AxiosHttpClient {
|
|
8
|
+
private readonly base;
|
|
9
|
+
private readonly client;
|
|
10
|
+
private readonly resolveEndpoint;
|
|
11
|
+
constructor(base: string, client: AxiosInstance, resolveEndpoint?: HttpEndpointResolver);
|
|
12
|
+
protected httpGet<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema): (url: string, config?: AxiosRequestConfig) => Promise<z.infer<TSchema>>;
|
|
13
|
+
protected httpGetBlob(): (url: string, config?: AxiosRequestConfig) => Promise<Blob>;
|
|
14
|
+
protected httpGetPageable<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema): (url: string, pageable: PageableParams, config?: AxiosRequestConfig) => Promise<PageableResponse<z.infer<TSchema>>>;
|
|
15
|
+
protected httpPost<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema): (url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<z.infer<TSchema>>;
|
|
16
|
+
protected httpPut<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema): (url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<z.infer<TSchema>>;
|
|
17
|
+
protected httpDelete<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema): (url: string, config?: AxiosRequestConfig) => Promise<z.infer<TSchema>>;
|
|
18
|
+
private doGet;
|
|
19
|
+
private doGetPageable;
|
|
20
|
+
private doPost;
|
|
21
|
+
private doPut;
|
|
22
|
+
private doDelete;
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
export type SanitizedAxiosError = {
|
|
3
|
+
code?: string;
|
|
4
|
+
message: string;
|
|
5
|
+
method?: string;
|
|
6
|
+
name: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
status?: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function getAxiosRequestPath(error: AxiosError): string | undefined;
|
|
11
|
+
export declare function sanitizeAxiosError(error: AxiosError): SanitizedAxiosError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isRequestCanceled(error: unknown): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { default as z } from 'zod';
|
|
3
|
+
import { HttpEndpointResolver } from './AxiosHttpClient';
|
|
4
|
+
import { PageableParams, PageableResponse } from './pagination';
|
|
5
|
+
export declare function createPageableResponseSchema<TSchema extends z.ZodTypeAny>(contentSchema: TSchema): z.ZodObject<{
|
|
6
|
+
content: z.ZodArray<TSchema>;
|
|
7
|
+
number: z.ZodNumber;
|
|
8
|
+
size: z.ZodNumber;
|
|
9
|
+
totalElements: z.ZodNumber;
|
|
10
|
+
totalPages: z.ZodNumber;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type SearchableFilters = {
|
|
13
|
+
searchText: string;
|
|
14
|
+
queryFiltersJson?: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type PagedSearchRequest<TEntity, TFilters extends SearchableFilters> = {
|
|
17
|
+
client: Pick<AxiosInstance, "post">;
|
|
18
|
+
endpointName: string;
|
|
19
|
+
schema: z.ZodType<TEntity>;
|
|
20
|
+
pageable: PageableParams;
|
|
21
|
+
filters: TFilters;
|
|
22
|
+
config?: AxiosRequestConfig;
|
|
23
|
+
resolveEndpoint?: HttpEndpointResolver;
|
|
24
|
+
};
|
|
25
|
+
export declare function normalizePageableResponse<T>(response: PageableResponse<T>, fallbackPageable: PageableParams): PageableResponse<T>;
|
|
26
|
+
export declare function emptyPageableResponse<T>(pageable: PageableParams): PageableResponse<T>;
|
|
27
|
+
export declare function sortLocalResultsByAccessor<T>(items: T[], sortDirection: string | undefined, getValue: (item: T) => number | string): T[];
|
|
28
|
+
export declare function parseQueryFilterRequest(filtersJson: string | null): unknown | undefined;
|
|
29
|
+
export declare function postPagedSearch<TEntity, TFilters extends SearchableFilters>({ client, endpointName, schema, pageable, filters, config, resolveEndpoint, }: PagedSearchRequest<TEntity, TFilters>): Promise<PageableResponse<TEntity>>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type PageableParams = {
|
|
2
|
+
page: number;
|
|
3
|
+
rowsPerPage: number;
|
|
4
|
+
sortBy: string;
|
|
5
|
+
sortDirection: "asc" | "desc";
|
|
6
|
+
};
|
|
7
|
+
export type PageableResponse<T> = {
|
|
8
|
+
content: T[];
|
|
9
|
+
number: number;
|
|
10
|
+
size: number;
|
|
11
|
+
totalElements: number;
|
|
12
|
+
totalPages: number;
|
|
13
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SessionExpiryState = Readonly<{
|
|
2
|
+
manualLogoutPending: boolean;
|
|
3
|
+
notificationPending: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export type SessionExpiryChannelOptions = {
|
|
6
|
+
onListenerError?: (error: unknown) => void;
|
|
7
|
+
};
|
|
8
|
+
export type SessionExpiryChannel = {
|
|
9
|
+
beginManualLogout: () => void;
|
|
10
|
+
cancelManualLogout: () => void;
|
|
11
|
+
getState: () => SessionExpiryState;
|
|
12
|
+
notifySessionExpired: () => boolean;
|
|
13
|
+
reset: () => void;
|
|
14
|
+
subscribe: (listener: () => void) => () => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Creates an isolated session-expiry coordination channel.
|
|
18
|
+
*
|
|
19
|
+
* Applications own the instance and decide how a notification changes routing
|
|
20
|
+
* or authentication state. Infrastructure only deduplicates notifications and
|
|
21
|
+
* suppresses expiry handling while an intentional logout is pending.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSessionExpiryChannel(options?: SessionExpiryChannelOptions): SessionExpiryChannel;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './api/createModeAwareApi';
|
|
2
|
+
export * from './api/transactional';
|
|
3
|
+
export * from './errors/AppOfflineQueuedError';
|
|
4
|
+
export * from './errors/OfflineModeNotSupportedError';
|
|
5
|
+
export * from './http/AxiosHttpClient';
|
|
6
|
+
export * from './http/axiosErrorReporting';
|
|
7
|
+
export * from './http/isRequestCanceled';
|
|
8
|
+
export * from './http/pagination';
|
|
9
|
+
export * from './http/pagedSearch';
|
|
10
|
+
export * from './http/sessionExpiry';
|
|
11
|
+
export * from './network/appNetworkStatus';
|
|
12
|
+
export * from './network/createConnectivityState';
|
|
13
|
+
export * from './signals/createPersistentSignal';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import "./http/pagination.js";
|
|
2
|
+
import { AppOfflineError as e, getAppOnlineStatus as t, isAppOfflineError as n, subscribeToAppNetworkStatus as r } from "./network/appNetworkStatus.js";
|
|
3
|
+
import i from "zod";
|
|
4
|
+
import a, { CanceledError as o } from "axios";
|
|
5
|
+
import { computed as s, signal as c } from "@preact/signals-core";
|
|
6
|
+
//#region src/api/transactional.ts
|
|
7
|
+
var l = Symbol("transactional-method-metadata");
|
|
8
|
+
function u(e) {
|
|
9
|
+
return typeof e == "function" && e[l] === !0;
|
|
10
|
+
}
|
|
11
|
+
function d() {
|
|
12
|
+
return function(e, t) {
|
|
13
|
+
if (t.private) throw Error("@transactional cannot decorate private methods.");
|
|
14
|
+
return Object.defineProperty(e, l, {
|
|
15
|
+
value: !0,
|
|
16
|
+
configurable: !1,
|
|
17
|
+
enumerable: !1,
|
|
18
|
+
writable: !1
|
|
19
|
+
}), e;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/api/createModeAwareApi.ts
|
|
24
|
+
function f(e) {
|
|
25
|
+
if (e == null || typeof e != "object" && typeof e != "function") return [];
|
|
26
|
+
let t = /* @__PURE__ */ new Set(), n = e;
|
|
27
|
+
for (; n && n !== Object.prototype;) {
|
|
28
|
+
for (let e of Object.getOwnPropertyNames(n)) {
|
|
29
|
+
if (e === "constructor") continue;
|
|
30
|
+
let r = Object.getOwnPropertyDescriptor(n, e);
|
|
31
|
+
r && typeof r.value == "function" && t.add(e);
|
|
32
|
+
}
|
|
33
|
+
n = Object.getPrototypeOf(n);
|
|
34
|
+
}
|
|
35
|
+
return [...t];
|
|
36
|
+
}
|
|
37
|
+
function p(e, t) {
|
|
38
|
+
if (e == null || typeof e != "object" && typeof e != "function") return;
|
|
39
|
+
let n = Reflect.get(e, t);
|
|
40
|
+
return typeof n == "function" ? n : void 0;
|
|
41
|
+
}
|
|
42
|
+
async function m(e, t, n) {
|
|
43
|
+
return await t.apply(e, n);
|
|
44
|
+
}
|
|
45
|
+
function h({ onlineApi: e, offlineApi: t, readOnline: n, assertCanInvoke: r, isOfflineTransactional: i = u, isOfflineFallbackError: a = () => !1, createNoFallbackError: o = (e) => /* @__PURE__ */ Error(`[api] Offline mode is not supported and no online fallback exists for ${e.methodLabel}.`), now: s = Date.now, onInvokeSuccess: c, onInvokeError: l }) {
|
|
46
|
+
let d = {}, h = Object.keys(e);
|
|
47
|
+
async function g(e, t, n, r, i, a) {
|
|
48
|
+
let o = s();
|
|
49
|
+
try {
|
|
50
|
+
let a = await m(e, t, n);
|
|
51
|
+
return c?.({
|
|
52
|
+
...r,
|
|
53
|
+
mode: i,
|
|
54
|
+
durationMs: Math.round(s() - o)
|
|
55
|
+
}), a;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
throw a?.(e) || l?.({
|
|
58
|
+
...r,
|
|
59
|
+
mode: i,
|
|
60
|
+
durationMs: Math.round(s() - o)
|
|
61
|
+
}, e), e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (let s of h) {
|
|
65
|
+
let c = e[s], u = t[s], m = /* @__PURE__ */ new Set([...f(c), ...f(u)]);
|
|
66
|
+
if (m.size === 0) throw Error(`[api] Module ${String(s)} has no callable handlers. Define at least one API method in offline or online implementation.`);
|
|
67
|
+
let h = {};
|
|
68
|
+
for (let e of m) {
|
|
69
|
+
let t = p(u, e), d = p(c, e), f = `${String(s)}.${e}`;
|
|
70
|
+
if (!t && !d) throw Error(`[api] Missing handlers for ${f}. Define at least one offline or online implementation.`);
|
|
71
|
+
h[e] = async (...p) => {
|
|
72
|
+
let m = n(), h = {
|
|
73
|
+
moduleKey: String(s),
|
|
74
|
+
methodName: e,
|
|
75
|
+
methodLabel: f,
|
|
76
|
+
online: m
|
|
77
|
+
};
|
|
78
|
+
if (r?.(h), t && i(t, h)) {
|
|
79
|
+
let e = m && !!d;
|
|
80
|
+
return await g(e ? c : u, e ? d : t, p, h, e ? "online" : "offline");
|
|
81
|
+
}
|
|
82
|
+
if (d && m) return await g(c, d, p, h, "online");
|
|
83
|
+
if (t) {
|
|
84
|
+
let e = !1;
|
|
85
|
+
try {
|
|
86
|
+
return await g(u, t, p, h, "offline", (t) => (e = a(t, h), e));
|
|
87
|
+
} catch (t) {
|
|
88
|
+
if (!e) throw t;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (d && !m) return await g(c, d, p, h, "online");
|
|
92
|
+
let _ = o(h);
|
|
93
|
+
throw l?.({
|
|
94
|
+
...h,
|
|
95
|
+
mode: "online",
|
|
96
|
+
durationMs: 0
|
|
97
|
+
}, _), _;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
d[s] = h;
|
|
101
|
+
}
|
|
102
|
+
return d;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/errors/AppOfflineQueuedError.ts
|
|
106
|
+
var g = class extends Error {
|
|
107
|
+
constructor() {
|
|
108
|
+
super("The action was queued for synchronization."), this.name = "AppOfflineQueuedError";
|
|
109
|
+
}
|
|
110
|
+
}, _ = class extends Error {
|
|
111
|
+
constructor(e) {
|
|
112
|
+
super(`Offline mode does not support this operation yet: ${e}`), this.name = "OfflineModeNotSupportedError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/http/pagedSearch.ts
|
|
117
|
+
function v(e) {
|
|
118
|
+
let t = i.number().int().nonnegative();
|
|
119
|
+
return i.object({
|
|
120
|
+
content: i.array(e),
|
|
121
|
+
number: t,
|
|
122
|
+
size: t,
|
|
123
|
+
totalElements: t,
|
|
124
|
+
totalPages: t
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function y(e, t) {
|
|
128
|
+
let n = Array.isArray(e?.content) ? e.content : [], r = (e, t) => Number.isFinite(e) && e >= 0 ? Math.floor(e) : t, i = r(t.page, 0), a = r(t.rowsPerPage, n.length), o = r(e?.number, i), s = r(e?.size, a), c = r(e?.totalElements, n.length), l = Number.isFinite(e?.totalPages) && e.totalPages >= 0 ? Math.floor(e.totalPages) : s > 0 ? Math.ceil(c / s) : 0;
|
|
129
|
+
return {
|
|
130
|
+
...e,
|
|
131
|
+
content: n,
|
|
132
|
+
number: o,
|
|
133
|
+
size: s,
|
|
134
|
+
totalElements: c,
|
|
135
|
+
totalPages: l
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function b(e) {
|
|
139
|
+
return y({
|
|
140
|
+
content: [],
|
|
141
|
+
number: 0,
|
|
142
|
+
size: 0,
|
|
143
|
+
totalElements: 0,
|
|
144
|
+
totalPages: 0
|
|
145
|
+
}, e);
|
|
146
|
+
}
|
|
147
|
+
function x(e, t, n) {
|
|
148
|
+
let r = t === "desc" ? -1 : 1;
|
|
149
|
+
return [...e].sort((e, t) => {
|
|
150
|
+
let i = n(e), a = n(t);
|
|
151
|
+
return typeof i == "number" && typeof a == "number" ? (i - a) * r : String(i).localeCompare(String(a)) * r;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function S(e) {
|
|
155
|
+
if (!e || !e.trim()) return;
|
|
156
|
+
let t = i.object({ rows: i.array(i.unknown()) }).passthrough().parse(JSON.parse(e));
|
|
157
|
+
return t.rows.length === 0 ? void 0 : t;
|
|
158
|
+
}
|
|
159
|
+
async function C({ client: e, endpointName: t, schema: n, pageable: r, filters: i, config: a, resolveEndpoint: o = w }) {
|
|
160
|
+
let s = await e.post(o(t, "search"), S(i.queryFiltersJson ?? null) ?? null, {
|
|
161
|
+
...a,
|
|
162
|
+
params: {
|
|
163
|
+
...r,
|
|
164
|
+
...a?.params,
|
|
165
|
+
searchText: i.searchText
|
|
166
|
+
},
|
|
167
|
+
headers: {
|
|
168
|
+
...a?.headers,
|
|
169
|
+
"Content-Type": "application/json"
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return T(v(n), s.data);
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/http/AxiosHttpClient.ts
|
|
176
|
+
function w(e, ...t) {
|
|
177
|
+
return `/${[e.replace(/^\/+|\/+$/g, ""), ...t.map((e) => String(e).replace(/^\/+|\/+$/g, "")).filter(Boolean)].filter(Boolean).join("/")}`;
|
|
178
|
+
}
|
|
179
|
+
function T(e, t) {
|
|
180
|
+
return e.parse(t);
|
|
181
|
+
}
|
|
182
|
+
var E = class {
|
|
183
|
+
constructor(e, t, n = w) {
|
|
184
|
+
this.base = e, this.client = t, this.resolveEndpoint = n;
|
|
185
|
+
}
|
|
186
|
+
httpGet(e) {
|
|
187
|
+
return async (t, n) => {
|
|
188
|
+
let r = await this.doGet(t, n);
|
|
189
|
+
return T(e ?? i.unknown(), r);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
httpGetBlob() {
|
|
193
|
+
return async (e, t) => (await this.client.get(this.resolveEndpoint(this.base, e), {
|
|
194
|
+
...t,
|
|
195
|
+
responseType: "blob"
|
|
196
|
+
})).data;
|
|
197
|
+
}
|
|
198
|
+
httpGetPageable(e) {
|
|
199
|
+
return async (t, n, r) => {
|
|
200
|
+
let a = await this.doGetPageable(t, n, r);
|
|
201
|
+
return T(v(e ?? i.unknown()), a);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
httpPost(e) {
|
|
205
|
+
return async (t, n, r) => {
|
|
206
|
+
let a = await this.doPost(t, n, r);
|
|
207
|
+
return T(e ?? i.unknown(), a);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
httpPut(e) {
|
|
211
|
+
return async (t, n, r) => {
|
|
212
|
+
let a = await this.doPut(t, n, r);
|
|
213
|
+
return T(e ?? i.unknown(), a);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
httpDelete(e) {
|
|
217
|
+
return async (t, n) => {
|
|
218
|
+
let r = await this.doDelete(t, n);
|
|
219
|
+
return T(e ?? i.unknown(), r);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async doGet(e, t) {
|
|
223
|
+
return (await this.client.get(this.resolveEndpoint(this.base, e), t)).data;
|
|
224
|
+
}
|
|
225
|
+
async doGetPageable(e, t, n) {
|
|
226
|
+
return (await this.client.get(this.resolveEndpoint(this.base, e), {
|
|
227
|
+
...n,
|
|
228
|
+
params: {
|
|
229
|
+
...t,
|
|
230
|
+
...n?.params
|
|
231
|
+
}
|
|
232
|
+
})).data;
|
|
233
|
+
}
|
|
234
|
+
async doPost(e, t, n) {
|
|
235
|
+
return (await this.client.post(this.resolveEndpoint(this.base, e), t, n)).data;
|
|
236
|
+
}
|
|
237
|
+
async doPut(e, t, n) {
|
|
238
|
+
return (await this.client.put(this.resolveEndpoint(this.base, e), t, n)).data;
|
|
239
|
+
}
|
|
240
|
+
async doDelete(e, t) {
|
|
241
|
+
return (await this.client.delete(this.resolveEndpoint(this.base, e), t)).data;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/http/axiosErrorReporting.ts
|
|
246
|
+
function D(e) {
|
|
247
|
+
let t = e.config?.url;
|
|
248
|
+
if (t) try {
|
|
249
|
+
let n = e.config?.baseURL ? new URL(e.config.baseURL, "http://localhost").toString() : "http://localhost";
|
|
250
|
+
return new URL(t, n).pathname;
|
|
251
|
+
} catch {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function O(e) {
|
|
256
|
+
return {
|
|
257
|
+
name: e.name,
|
|
258
|
+
message: e.message,
|
|
259
|
+
code: e.code,
|
|
260
|
+
status: e.response?.status,
|
|
261
|
+
method: e.config?.method?.toUpperCase(),
|
|
262
|
+
path: D(e)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/http/isRequestCanceled.ts
|
|
267
|
+
function k(e) {
|
|
268
|
+
return a.isCancel(e) || e instanceof o || a.isAxiosError(e) && e.code === "ERR_CANCELED" || typeof DOMException < "u" && e instanceof DOMException && e.name === "AbortError";
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/http/sessionExpiry.ts
|
|
272
|
+
function A(e = {}) {
|
|
273
|
+
let t = /* @__PURE__ */ new Set(), n = !1, r = !1;
|
|
274
|
+
return {
|
|
275
|
+
beginManualLogout() {
|
|
276
|
+
n = !0;
|
|
277
|
+
},
|
|
278
|
+
cancelManualLogout() {
|
|
279
|
+
n = !1;
|
|
280
|
+
},
|
|
281
|
+
getState() {
|
|
282
|
+
return {
|
|
283
|
+
manualLogoutPending: n,
|
|
284
|
+
notificationPending: r
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
notifySessionExpired() {
|
|
288
|
+
return n || r ? !1 : (r = !0, [...t].forEach((t) => {
|
|
289
|
+
try {
|
|
290
|
+
t();
|
|
291
|
+
} catch (t) {
|
|
292
|
+
e.onListenerError?.(t);
|
|
293
|
+
}
|
|
294
|
+
}), !0);
|
|
295
|
+
},
|
|
296
|
+
reset() {
|
|
297
|
+
n = !1, r = !1;
|
|
298
|
+
},
|
|
299
|
+
subscribe(e) {
|
|
300
|
+
return t.add(e), () => t.delete(e);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/network/createConnectivityState.ts
|
|
306
|
+
function j(e, t, n) {
|
|
307
|
+
if (!Number.isFinite(t) || (n ? t < 0 : t <= 0)) throw RangeError(`${e} must be a finite ${n ? "non-negative" : "positive"} number`);
|
|
308
|
+
}
|
|
309
|
+
function M({ initialBrowserOnline: e, heartbeatStaleAfterMs: t, heartbeatBootstrapAssumeOnlineMs: n, runtimeIntervalMs: r = 1e3, now: i = Date.now }) {
|
|
310
|
+
j("heartbeatStaleAfterMs", t, !1), j("heartbeatBootstrapAssumeOnlineMs", n, !0), j("runtimeIntervalMs", r, !1);
|
|
311
|
+
let a = c(e), o = c(!1), l = c(null), u = c(null), d = c(!1), f = c(!1), p = c(null), m = c(0), h = c(null), g = c(0), _ = null, v = s(() => {
|
|
312
|
+
if (m.value, u.value === !1 || !a.value) return !1;
|
|
313
|
+
if (!o.value) return !0;
|
|
314
|
+
let e = p.value;
|
|
315
|
+
if (e == null) {
|
|
316
|
+
let e = l.value;
|
|
317
|
+
return e != null && i() - e <= n;
|
|
318
|
+
}
|
|
319
|
+
return i() - e <= t;
|
|
320
|
+
}), y = s(() => ({
|
|
321
|
+
online: v.value,
|
|
322
|
+
heartbeatEnabled: o.value,
|
|
323
|
+
heartbeatConnected: d.value,
|
|
324
|
+
syncInProgress: f.value,
|
|
325
|
+
lastHeartbeatAt: p.value
|
|
326
|
+
}));
|
|
327
|
+
function b() {
|
|
328
|
+
g.value += 1, h.value = i();
|
|
329
|
+
}
|
|
330
|
+
function x(e) {
|
|
331
|
+
o.value !== e && (o.value = e, l.value = e ? i() : null, u.value = null, e || (d.value = !1, p.value = null, f.value = !1, h.value = null));
|
|
332
|
+
}
|
|
333
|
+
function S() {
|
|
334
|
+
u.value = !0, d.value = !0, p.value = i(), h.value = null;
|
|
335
|
+
}
|
|
336
|
+
function C() {
|
|
337
|
+
d.value = !1;
|
|
338
|
+
}
|
|
339
|
+
function w(e) {
|
|
340
|
+
u.value = !0, d.value = !0, p.value = i(), f.value = e, h.value = null;
|
|
341
|
+
}
|
|
342
|
+
function T() {
|
|
343
|
+
u.value = !1;
|
|
344
|
+
}
|
|
345
|
+
function E() {
|
|
346
|
+
u.value = !0;
|
|
347
|
+
}
|
|
348
|
+
function D() {
|
|
349
|
+
if (!o.value) return;
|
|
350
|
+
m.value += 1;
|
|
351
|
+
let e = i(), n = p.value, r = l.value, a = n == null ? null : e - n, s = r == null ? null : e - r;
|
|
352
|
+
if (!(a == null ? s != null && s > t : a > t)) return;
|
|
353
|
+
C();
|
|
354
|
+
let c = h.value;
|
|
355
|
+
(c == null || e - c > t) && b();
|
|
356
|
+
}
|
|
357
|
+
function O(e) {
|
|
358
|
+
if (_) throw Error("Connectivity runtime has already been started");
|
|
359
|
+
a.value = e.readBrowserOnline();
|
|
360
|
+
let t = e.subscribeBrowserOnline((e) => {
|
|
361
|
+
a.value = e;
|
|
362
|
+
}), n;
|
|
363
|
+
try {
|
|
364
|
+
n = e.scheduleRepeating(D, r);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
throw t(), e;
|
|
367
|
+
}
|
|
368
|
+
let i = () => {
|
|
369
|
+
_ === i && (_ = null, t(), n());
|
|
370
|
+
};
|
|
371
|
+
return _ = i, i;
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
sigOnline: v,
|
|
375
|
+
sigSnapshot: y,
|
|
376
|
+
sigReconnectRequestTick: g,
|
|
377
|
+
setHeartbeatEnabled: x,
|
|
378
|
+
markHeartbeatConnected: S,
|
|
379
|
+
markHeartbeatDisconnected: C,
|
|
380
|
+
markHeartbeatReceived: w,
|
|
381
|
+
markBackendUnavailable: T,
|
|
382
|
+
markBackendAvailable: E,
|
|
383
|
+
startRuntime: O
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/signals/createPersistentSignal.ts
|
|
388
|
+
function N(e, t) {
|
|
389
|
+
let n = c(e.get(t));
|
|
390
|
+
return {
|
|
391
|
+
signal: n,
|
|
392
|
+
setLocal: (r) => {
|
|
393
|
+
e.set(t, r), n.value = r;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
398
|
+
export { e as AppOfflineError, g as AppOfflineQueuedError, E as AxiosHttpClient, _ as OfflineModeNotSupportedError, M as createConnectivityState, h as createModeAwareApi, v as createPageableResponseSchema, N as createPersistentSignal, A as createSessionExpiryChannel, b as emptyPageableResponse, t as getAppOnlineStatus, D as getAxiosRequestPath, u as getTransactionalMetadata, n as isAppOfflineError, k as isRequestCanceled, y as normalizePageableResponse, T as parseHttpResponse, S as parseQueryFilterRequest, C as postPagedSearch, w as resolveHttpEndpoint, O as sanitizeAxiosError, x as sortLocalResultsByAccessor, r as subscribeToAppNetworkStatus, d as transactional };
|
|
399
|
+
|
|
400
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/api/transactional.ts","../src/api/createModeAwareApi.ts","../src/errors/AppOfflineQueuedError.ts","../src/errors/OfflineModeNotSupportedError.ts","../src/http/pagedSearch.ts","../src/http/AxiosHttpClient.ts","../src/http/axiosErrorReporting.ts","../src/http/isRequestCanceled.ts","../src/http/sessionExpiry.ts","../src/network/createConnectivityState.ts","../src/signals/createPersistentSignal.ts"],"sourcesContent":["type AsyncMethod<TThis, TArgs extends unknown[], TResult> = (this: TThis, ...args: TArgs) => Promise<TResult>;\n\nconst TRANSACTIONAL_METHOD_METADATA = Symbol(\"transactional-method-metadata\");\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\ntype TransactionalMethod = Function & {\n [TRANSACTIONAL_METHOD_METADATA]?: boolean;\n};\n\nexport function getTransactionalMetadata(value: unknown): boolean {\n if (typeof value !== \"function\") {\n return false;\n }\n\n return (value as TransactionalMethod)[TRANSACTIONAL_METHOD_METADATA] === true;\n}\n\nexport function transactional<TThis, TArgs extends unknown[], TResult>() {\n return function (\n originalMethod: AsyncMethod<TThis, TArgs, TResult>,\n context: ClassMethodDecoratorContext<TThis, AsyncMethod<TThis, TArgs, TResult>>,\n ) {\n if (context.private) {\n throw new Error(\"@transactional cannot decorate private methods.\");\n }\n\n Object.defineProperty(originalMethod, TRANSACTIONAL_METHOD_METADATA, {\n value: true,\n configurable: false,\n enumerable: false,\n writable: false,\n });\n\n return originalMethod;\n };\n}\n","import { getTransactionalMetadata } from \"./transactional\";\n\ntype AsyncMethod = (...args: unknown[]) => Promise<unknown>;\n\nexport type ModeAwareApiMode = \"offline\" | \"online\";\n\nexport type ModeAwareApiInvocationContext = {\n moduleKey: string;\n methodName: string;\n methodLabel: string;\n online: boolean;\n};\n\nexport type ModeAwareApiInvocationEvent = ModeAwareApiInvocationContext & {\n mode: ModeAwareApiMode;\n durationMs: number;\n};\n\nexport type CreateModeAwareApiOptions<\n TOnlineApi extends Record<string, unknown>,\n TOfflineApi extends Partial<{ [TModuleKey in keyof TOnlineApi]: unknown }>,\n> = {\n onlineApi: TOnlineApi;\n offlineApi: TOfflineApi;\n readOnline: () => boolean;\n assertCanInvoke?: (context: ModeAwareApiInvocationContext) => void;\n isOfflineTransactional?: (offlineMethod: unknown, context: ModeAwareApiInvocationContext) => boolean;\n isOfflineFallbackError?: (error: unknown, context: ModeAwareApiInvocationContext) => boolean;\n createNoFallbackError?: (context: ModeAwareApiInvocationContext) => Error;\n now?: () => number;\n onInvokeSuccess?: (event: ModeAwareApiInvocationEvent) => void;\n onInvokeError?: (event: ModeAwareApiInvocationEvent, error: unknown) => void;\n};\n\nfunction getFunctionMemberNames(moduleObject: unknown): string[] {\n if (moduleObject == null || (typeof moduleObject !== \"object\" && typeof moduleObject !== \"function\")) {\n return [];\n }\n\n const members = new Set<string>();\n let current = moduleObject as object | null;\n\n while (current && current !== Object.prototype) {\n for (const name of Object.getOwnPropertyNames(current)) {\n if (name === \"constructor\") {\n continue;\n }\n\n const descriptor = Object.getOwnPropertyDescriptor(current, name);\n if (descriptor && typeof descriptor.value === \"function\") {\n members.add(name);\n }\n }\n\n current = Object.getPrototypeOf(current) as object | null;\n }\n\n return [...members];\n}\n\nfunction getModuleMethod(moduleObject: unknown, methodName: PropertyKey): AsyncMethod | undefined {\n if (moduleObject == null || (typeof moduleObject !== \"object\" && typeof moduleObject !== \"function\")) {\n return undefined;\n }\n\n const candidate = Reflect.get(moduleObject, methodName);\n return typeof candidate === \"function\" ? (candidate as AsyncMethod) : undefined;\n}\n\nasync function invokeModuleMethod(moduleObject: unknown, method: AsyncMethod, args: unknown[]): Promise<unknown> {\n return await method.apply(moduleObject, args);\n}\n\nexport function createModeAwareApi<\n TOnlineApi extends Record<string, unknown>,\n TOfflineApi extends Partial<{ [TModuleKey in keyof TOnlineApi]: unknown }>,\n>({\n onlineApi,\n offlineApi,\n readOnline,\n assertCanInvoke,\n isOfflineTransactional = getTransactionalMetadata,\n isOfflineFallbackError = () => false,\n createNoFallbackError = context =>\n new Error(`[api] Offline mode is not supported and no online fallback exists for ${context.methodLabel}.`),\n now = Date.now,\n onInvokeSuccess,\n onInvokeError,\n}: CreateModeAwareApiOptions<TOnlineApi, TOfflineApi>): TOnlineApi {\n const resolved = {} as TOnlineApi;\n const moduleKeys = Object.keys(onlineApi) as Array<keyof TOnlineApi>;\n\n async function invoke(\n moduleObject: unknown,\n method: AsyncMethod,\n args: unknown[],\n context: ModeAwareApiInvocationContext,\n mode: ModeAwareApiMode,\n suppressError?: (error: unknown) => boolean,\n ): Promise<unknown> {\n const startedAt = now();\n\n try {\n const result = await invokeModuleMethod(moduleObject, method, args);\n onInvokeSuccess?.({ ...context, mode, durationMs: Math.round(now() - startedAt) });\n return result;\n } catch (error) {\n if (!suppressError?.(error)) {\n onInvokeError?.({ ...context, mode, durationMs: Math.round(now() - startedAt) }, error);\n }\n throw error;\n }\n }\n\n for (const moduleKey of moduleKeys) {\n const onlineModule = onlineApi[moduleKey];\n const offlineModule = offlineApi[moduleKey];\n const methodNames = new Set<string>([\n ...getFunctionMemberNames(onlineModule),\n ...getFunctionMemberNames(offlineModule),\n ]);\n\n if (methodNames.size === 0) {\n throw new Error(\n `[api] Module ${String(moduleKey)} has no callable handlers. Define at least one API method in offline or online implementation.`,\n );\n }\n\n const resolvedModule: Record<string, unknown> = {};\n\n for (const methodName of methodNames) {\n const offlineMethod = getModuleMethod(offlineModule, methodName);\n const onlineMethod = getModuleMethod(onlineModule, methodName);\n const methodLabel = `${String(moduleKey)}.${methodName}`;\n\n if (!offlineMethod && !onlineMethod) {\n throw new Error(\n `[api] Missing handlers for ${methodLabel}. Define at least one offline or online implementation.`,\n );\n }\n\n resolvedModule[methodName] = async (...args: unknown[]) => {\n const online = readOnline();\n const context: ModeAwareApiInvocationContext = {\n moduleKey: String(moduleKey),\n methodName,\n methodLabel,\n online,\n };\n\n assertCanInvoke?.(context);\n\n if (offlineMethod && isOfflineTransactional(offlineMethod, context)) {\n const useOnline = online && !!onlineMethod;\n return await invoke(\n useOnline ? onlineModule : offlineModule,\n useOnline ? onlineMethod : offlineMethod,\n args,\n context,\n useOnline ? \"online\" : \"offline\",\n );\n }\n\n if (onlineMethod && online) {\n return await invoke(onlineModule, onlineMethod, args, context, \"online\");\n }\n\n if (offlineMethod) {\n let shouldFallback = false;\n\n try {\n return await invoke(offlineModule, offlineMethod, args, context, \"offline\", error => {\n shouldFallback = isOfflineFallbackError(error, context);\n return shouldFallback;\n });\n } catch (error) {\n if (!shouldFallback) {\n throw error;\n }\n }\n }\n\n if (onlineMethod && !online) {\n return await invoke(onlineModule, onlineMethod, args, context, \"online\");\n }\n\n const fallbackError = createNoFallbackError(context);\n onInvokeError?.({ ...context, mode: \"online\", durationMs: 0 }, fallbackError);\n throw fallbackError;\n };\n }\n\n (resolved as Record<string, unknown>)[moduleKey as string] = resolvedModule;\n }\n\n return resolved;\n}\n","export class AppOfflineQueuedError extends Error {\n constructor() {\n super(\"The action was queued for synchronization.\");\n this.name = \"AppOfflineQueuedError\";\n }\n}\n","export class OfflineModeNotSupportedError extends Error {\n constructor(operation: string) {\n super(`Offline mode does not support this operation yet: ${operation}`);\n this.name = \"OfflineModeNotSupportedError\";\n }\n}\n","import { type AxiosInstance, type AxiosRequestConfig } from \"axios\";\nimport z from \"zod\";\nimport { parseHttpResponse, resolveHttpEndpoint, type HttpEndpointResolver } from \"./AxiosHttpClient\";\nimport { type PageableParams, type PageableResponse } from \"./pagination\";\n\nexport function createPageableResponseSchema<TSchema extends z.ZodTypeAny>(contentSchema: TSchema) {\n const metadata = z.number().int().nonnegative();\n\n return z.object({\n content: z.array(contentSchema),\n number: metadata,\n size: metadata,\n totalElements: metadata,\n totalPages: metadata,\n });\n}\n\nexport type SearchableFilters = {\n searchText: string;\n queryFiltersJson?: string | null;\n};\n\nexport type PagedSearchRequest<TEntity, TFilters extends SearchableFilters> = {\n client: Pick<AxiosInstance, \"post\">;\n endpointName: string;\n schema: z.ZodType<TEntity>;\n pageable: PageableParams;\n filters: TFilters;\n config?: AxiosRequestConfig;\n resolveEndpoint?: HttpEndpointResolver;\n};\n\nexport function normalizePageableResponse<T>(\n response: PageableResponse<T>,\n fallbackPageable: PageableParams,\n): PageableResponse<T> {\n const content = Array.isArray(response?.content) ? response.content : [];\n const toNonnegativeInteger = (value: number, fallback: number) =>\n Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;\n const fallbackPage = toNonnegativeInteger(fallbackPageable.page, 0);\n const fallbackSize = toNonnegativeInteger(fallbackPageable.rowsPerPage, content.length);\n const number = toNonnegativeInteger(response?.number, fallbackPage);\n const size = toNonnegativeInteger(response?.size, fallbackSize);\n const totalElements = toNonnegativeInteger(response?.totalElements, content.length);\n const totalPages =\n Number.isFinite(response?.totalPages) && response.totalPages >= 0\n ? Math.floor(response.totalPages)\n : size > 0\n ? Math.ceil(totalElements / size)\n : 0;\n\n return { ...response, content, number, size, totalElements, totalPages };\n}\n\nexport function emptyPageableResponse<T>(pageable: PageableParams): PageableResponse<T> {\n return normalizePageableResponse({ content: [], number: 0, size: 0, totalElements: 0, totalPages: 0 }, pageable);\n}\n\nexport function sortLocalResultsByAccessor<T>(\n items: T[],\n sortDirection: string | undefined,\n getValue: (item: T) => number | string,\n): T[] {\n const direction = sortDirection === \"desc\" ? -1 : 1;\n\n return [...items].sort((left, right) => {\n const leftValue = getValue(left);\n const rightValue = getValue(right);\n\n if (typeof leftValue === \"number\" && typeof rightValue === \"number\") {\n return (leftValue - rightValue) * direction;\n }\n\n return String(leftValue).localeCompare(String(rightValue)) * direction;\n });\n}\n\nexport function parseQueryFilterRequest(filtersJson: string | null): unknown | undefined {\n if (!filtersJson || !filtersJson.trim()) {\n return undefined;\n }\n\n const parsed = z\n .object({ rows: z.array(z.unknown()) })\n .passthrough()\n .parse(JSON.parse(filtersJson));\n\n return parsed.rows.length === 0 ? undefined : parsed;\n}\n\nexport async function postPagedSearch<TEntity, TFilters extends SearchableFilters>({\n client,\n endpointName,\n schema,\n pageable,\n filters,\n config,\n resolveEndpoint = resolveHttpEndpoint,\n}: PagedSearchRequest<TEntity, TFilters>): Promise<PageableResponse<TEntity>> {\n const response = await client.post<PageableResponse<TEntity>>(\n resolveEndpoint(endpointName, \"search\"),\n parseQueryFilterRequest(filters.queryFiltersJson ?? null) ?? null,\n {\n ...config,\n params: { ...pageable, ...config?.params, searchText: filters.searchText },\n headers: { ...config?.headers, \"Content-Type\": \"application/json\" },\n },\n );\n\n return parseHttpResponse(createPageableResponseSchema(schema), response.data);\n}\n","import { type AxiosInstance, type AxiosRequestConfig } from \"axios\";\nimport z from \"zod\";\nimport { createPageableResponseSchema } from \"./pagedSearch\";\nimport { type PageableParams, type PageableResponse } from \"./pagination\";\n\nexport type HttpEndpointResolver = (base: string, ...segments: (number | string)[]) => string;\n\nexport function resolveHttpEndpoint(base: string, ...segments: (number | string)[]): string {\n const normalizedBase = base.replace(/^\\/+|\\/+$/g, \"\");\n const normalizedSegments = segments.map(segment => String(segment).replace(/^\\/+|\\/+$/g, \"\")).filter(Boolean);\n\n return `/${[normalizedBase, ...normalizedSegments].filter(Boolean).join(\"/\")}`;\n}\n\nexport function parseHttpResponse<TSchema extends z.ZodTypeAny>(schema: TSchema, data: unknown): z.infer<TSchema> {\n return schema.parse(data);\n}\n\nexport abstract class AxiosHttpClient {\n constructor(\n private readonly base: string,\n private readonly client: AxiosInstance,\n private readonly resolveEndpoint: HttpEndpointResolver = resolveHttpEndpoint,\n ) {}\n\n protected httpGet<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema) {\n return async (url: string, config?: AxiosRequestConfig): Promise<z.infer<TSchema>> => {\n const data = await this.doGet(url, config);\n return parseHttpResponse(schema ?? z.unknown(), data) as z.infer<TSchema>;\n };\n }\n\n protected httpGetBlob() {\n return async (url: string, config?: AxiosRequestConfig): Promise<Blob> => {\n const response = await this.client.get<Blob>(this.resolveEndpoint(this.base, url), {\n ...config,\n responseType: \"blob\",\n });\n return response.data;\n };\n }\n\n protected httpGetPageable<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema) {\n return async (\n url: string,\n pageable: PageableParams,\n config?: AxiosRequestConfig,\n ): Promise<PageableResponse<z.infer<TSchema>>> => {\n const response = await this.doGetPageable(url, pageable, config);\n return parseHttpResponse(createPageableResponseSchema(schema ?? z.unknown()), response) as PageableResponse<\n z.infer<TSchema>\n >;\n };\n }\n\n protected httpPost<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema) {\n return async (url: string, data?: unknown, config?: AxiosRequestConfig): Promise<z.infer<TSchema>> => {\n const responseData = await this.doPost(url, data, config);\n return parseHttpResponse(schema ?? z.unknown(), responseData) as z.infer<TSchema>;\n };\n }\n\n protected httpPut<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema) {\n return async (url: string, data?: unknown, config?: AxiosRequestConfig): Promise<z.infer<TSchema>> => {\n const responseData = await this.doPut(url, data, config);\n return parseHttpResponse(schema ?? z.unknown(), responseData) as z.infer<TSchema>;\n };\n }\n\n protected httpDelete<TSchema extends z.ZodTypeAny = z.ZodUnknown>(schema?: TSchema) {\n return async (url: string, config?: AxiosRequestConfig): Promise<z.infer<TSchema>> => {\n const responseData = await this.doDelete(url, config);\n return parseHttpResponse(schema ?? z.unknown(), responseData) as z.infer<TSchema>;\n };\n }\n\n private async doGet<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await this.client.get<T>(this.resolveEndpoint(this.base, url), config);\n return response.data;\n }\n\n private async doGetPageable<T>(\n url: string,\n pageable: PageableParams,\n config?: AxiosRequestConfig,\n ): Promise<PageableResponse<T>> {\n const response = await this.client.get<PageableResponse<T>>(this.resolveEndpoint(this.base, url), {\n ...config,\n params: {\n ...pageable,\n ...config?.params,\n },\n });\n return response.data;\n }\n\n private async doPost<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await this.client.post<T>(this.resolveEndpoint(this.base, url), data, config);\n return response.data;\n }\n\n private async doPut<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await this.client.put<T>(this.resolveEndpoint(this.base, url), data, config);\n return response.data;\n }\n\n private async doDelete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await this.client.delete<T>(this.resolveEndpoint(this.base, url), config);\n return response.data;\n }\n}\n","import { type AxiosError } from \"axios\";\n\nexport type SanitizedAxiosError = {\n code?: string;\n message: string;\n method?: string;\n name: string;\n path?: string;\n status?: number;\n};\n\nexport function getAxiosRequestPath(error: AxiosError): string | undefined {\n const requestUrl = error.config?.url;\n\n if (!requestUrl) {\n return undefined;\n }\n\n try {\n const baseUrl = error.config?.baseURL\n ? new URL(error.config.baseURL, \"http://localhost\").toString()\n : \"http://localhost\";\n return new URL(requestUrl, baseUrl).pathname;\n } catch {\n return undefined;\n }\n}\n\nexport function sanitizeAxiosError(error: AxiosError): SanitizedAxiosError {\n return {\n name: error.name,\n message: error.message,\n code: error.code,\n status: error.response?.status,\n method: error.config?.method?.toUpperCase(),\n path: getAxiosRequestPath(error),\n };\n}\n","import axios, { CanceledError } from \"axios\";\n\nexport function isRequestCanceled(error: unknown): boolean {\n return (\n axios.isCancel(error) ||\n error instanceof CanceledError ||\n (axios.isAxiosError(error) && error.code === \"ERR_CANCELED\") ||\n (typeof DOMException !== \"undefined\" && error instanceof DOMException && error.name === \"AbortError\")\n );\n}\n","export type SessionExpiryState = Readonly<{\n manualLogoutPending: boolean;\n notificationPending: boolean;\n}>;\n\nexport type SessionExpiryChannelOptions = {\n onListenerError?: (error: unknown) => void;\n};\n\nexport type SessionExpiryChannel = {\n beginManualLogout: () => void;\n cancelManualLogout: () => void;\n getState: () => SessionExpiryState;\n notifySessionExpired: () => boolean;\n reset: () => void;\n subscribe: (listener: () => void) => () => void;\n};\n\n/**\n * Creates an isolated session-expiry coordination channel.\n *\n * Applications own the instance and decide how a notification changes routing\n * or authentication state. Infrastructure only deduplicates notifications and\n * suppresses expiry handling while an intentional logout is pending.\n */\nexport function createSessionExpiryChannel(options: SessionExpiryChannelOptions = {}): SessionExpiryChannel {\n const listeners = new Set<() => void>();\n let manualLogoutPending = false;\n let notificationPending = false;\n\n return {\n beginManualLogout() {\n manualLogoutPending = true;\n },\n cancelManualLogout() {\n manualLogoutPending = false;\n },\n getState() {\n return { manualLogoutPending, notificationPending };\n },\n notifySessionExpired() {\n if (manualLogoutPending || notificationPending) {\n return false;\n }\n\n notificationPending = true;\n [...listeners].forEach(listener => {\n try {\n listener();\n } catch (error) {\n options.onListenerError?.(error);\n }\n });\n return true;\n },\n reset() {\n manualLogoutPending = false;\n notificationPending = false;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n","import { computed, signal } from \"@preact/signals-core\";\n\nexport type ConnectivitySnapshot = {\n online: boolean;\n heartbeatEnabled: boolean;\n heartbeatConnected: boolean;\n syncInProgress: boolean;\n lastHeartbeatAt: number | null;\n};\n\nexport type ConnectivityRuntimeAdapter = {\n readBrowserOnline: () => boolean;\n subscribeBrowserOnline: (listener: (online: boolean) => void) => () => void;\n scheduleRepeating: (callback: () => void, intervalMs: number) => () => void;\n};\n\nexport type ConnectivityStateOptions = {\n initialBrowserOnline: boolean;\n heartbeatStaleAfterMs: number;\n heartbeatBootstrapAssumeOnlineMs: number;\n runtimeIntervalMs?: number;\n now?: () => number;\n};\n\nfunction assertFiniteDuration(name: string, value: number, allowZero: boolean): void {\n if (!Number.isFinite(value) || (allowZero ? value < 0 : value <= 0)) {\n throw new RangeError(`${name} must be a finite ${allowZero ? \"non-negative\" : \"positive\"} number`);\n }\n}\n\nexport function createConnectivityState({\n initialBrowserOnline,\n heartbeatStaleAfterMs,\n heartbeatBootstrapAssumeOnlineMs,\n runtimeIntervalMs = 1_000,\n now = Date.now,\n}: ConnectivityStateOptions) {\n assertFiniteDuration(\"heartbeatStaleAfterMs\", heartbeatStaleAfterMs, false);\n assertFiniteDuration(\"heartbeatBootstrapAssumeOnlineMs\", heartbeatBootstrapAssumeOnlineMs, true);\n assertFiniteDuration(\"runtimeIntervalMs\", runtimeIntervalMs, false);\n\n const sigBrowserOnline = signal(initialBrowserOnline);\n const sigHeartbeatEnabled = signal(false);\n const sigHeartbeatEnabledAt = signal<number | null>(null);\n const sigBackendReachableOverride = signal<boolean | null>(null);\n const sigHeartbeatConnected = signal(false);\n const sigSyncInProgress = signal(false);\n const sigLastHeartbeatAt = signal<number | null>(null);\n const sigHeartbeatAgeTick = signal(0);\n const sigLastReconnectRequestAt = signal<number | null>(null);\n const sigReconnectRequestTick = signal(0);\n let activeRuntimeStop: (() => void) | null = null;\n\n const sigOnline = computed<boolean>(() => {\n void sigHeartbeatAgeTick.value;\n\n if (sigBackendReachableOverride.value === false || !sigBrowserOnline.value) {\n return false;\n }\n\n if (!sigHeartbeatEnabled.value) {\n return true;\n }\n\n const lastHeartbeatAt = sigLastHeartbeatAt.value;\n if (lastHeartbeatAt == null) {\n const heartbeatEnabledAt = sigHeartbeatEnabledAt.value;\n return heartbeatEnabledAt != null && now() - heartbeatEnabledAt <= heartbeatBootstrapAssumeOnlineMs;\n }\n\n return now() - lastHeartbeatAt <= heartbeatStaleAfterMs;\n });\n\n const sigSnapshot = computed<ConnectivitySnapshot>(() => ({\n online: sigOnline.value,\n heartbeatEnabled: sigHeartbeatEnabled.value,\n heartbeatConnected: sigHeartbeatConnected.value,\n syncInProgress: sigSyncInProgress.value,\n lastHeartbeatAt: sigLastHeartbeatAt.value,\n }));\n\n function requestReconnect(): void {\n sigReconnectRequestTick.value += 1;\n sigLastReconnectRequestAt.value = now();\n }\n\n function setHeartbeatEnabled(enabled: boolean): void {\n if (sigHeartbeatEnabled.value === enabled) {\n return;\n }\n\n sigHeartbeatEnabled.value = enabled;\n sigHeartbeatEnabledAt.value = enabled ? now() : null;\n sigBackendReachableOverride.value = null;\n\n if (!enabled) {\n sigHeartbeatConnected.value = false;\n sigLastHeartbeatAt.value = null;\n sigSyncInProgress.value = false;\n sigLastReconnectRequestAt.value = null;\n }\n }\n\n function markHeartbeatConnected(): void {\n sigBackendReachableOverride.value = true;\n sigHeartbeatConnected.value = true;\n sigLastHeartbeatAt.value = now();\n sigLastReconnectRequestAt.value = null;\n }\n\n function markHeartbeatDisconnected(): void {\n sigHeartbeatConnected.value = false;\n }\n\n function markHeartbeatReceived(syncInProgress: boolean): void {\n sigBackendReachableOverride.value = true;\n sigHeartbeatConnected.value = true;\n sigLastHeartbeatAt.value = now();\n sigSyncInProgress.value = syncInProgress;\n sigLastReconnectRequestAt.value = null;\n }\n\n function markBackendUnavailable(): void {\n sigBackendReachableOverride.value = false;\n }\n\n function markBackendAvailable(): void {\n sigBackendReachableOverride.value = true;\n }\n\n function refreshHeartbeatAge(): void {\n if (!sigHeartbeatEnabled.value) {\n return;\n }\n\n sigHeartbeatAgeTick.value += 1;\n const currentTime = now();\n const lastHeartbeatAt = sigLastHeartbeatAt.value;\n const heartbeatEnabledAt = sigHeartbeatEnabledAt.value;\n const heartbeatAge = lastHeartbeatAt == null ? null : currentTime - lastHeartbeatAt;\n const enabledAge = heartbeatEnabledAt == null ? null : currentTime - heartbeatEnabledAt;\n const heartbeatIsStale =\n heartbeatAge == null\n ? enabledAge != null && enabledAge > heartbeatStaleAfterMs\n : heartbeatAge > heartbeatStaleAfterMs;\n\n if (!heartbeatIsStale) {\n return;\n }\n\n markHeartbeatDisconnected();\n const lastReconnectRequestAt = sigLastReconnectRequestAt.value;\n if (lastReconnectRequestAt == null || currentTime - lastReconnectRequestAt > heartbeatStaleAfterMs) {\n requestReconnect();\n }\n }\n\n function startRuntime(adapter: ConnectivityRuntimeAdapter): () => void {\n if (activeRuntimeStop) {\n throw new Error(\"Connectivity runtime has already been started\");\n }\n\n sigBrowserOnline.value = adapter.readBrowserOnline();\n const unsubscribeBrowserOnline = adapter.subscribeBrowserOnline(online => {\n sigBrowserOnline.value = online;\n });\n let stopHeartbeatAgeChecks: () => void;\n try {\n stopHeartbeatAgeChecks = adapter.scheduleRepeating(refreshHeartbeatAge, runtimeIntervalMs);\n } catch (error) {\n unsubscribeBrowserOnline();\n throw error;\n }\n\n const stop = () => {\n if (activeRuntimeStop !== stop) {\n return;\n }\n\n activeRuntimeStop = null;\n unsubscribeBrowserOnline();\n stopHeartbeatAgeChecks();\n };\n activeRuntimeStop = stop;\n return stop;\n }\n\n return {\n sigOnline,\n sigSnapshot,\n sigReconnectRequestTick,\n setHeartbeatEnabled,\n markHeartbeatConnected,\n markHeartbeatDisconnected,\n markHeartbeatReceived,\n markBackendUnavailable,\n markBackendAvailable,\n startRuntime,\n };\n}\n","import { signal, type Signal } from \"@preact/signals-core\";\n\ntype Storage<TData extends Record<string, unknown>> = {\n get<TKey extends keyof TData>(key: TKey): TData[TKey];\n set<TKey extends keyof TData>(key: TKey, value: TData[TKey]): void;\n};\n\nexport type PersistentSignal<TData extends Record<string, unknown>, TKey extends keyof TData> = {\n signal: Signal<TData[TKey]>;\n setLocal: (value: TData[TKey]) => void;\n};\n\nexport function createPersistentSignal<TData extends Record<string, unknown>, TKey extends keyof TData>(\n storage: Storage<TData>,\n key: TKey,\n): PersistentSignal<TData, TKey> {\n const state = signal<TData[TKey]>(storage.get(key));\n\n const setLocal = (value: TData[TKey]): void => {\n storage.set(key, value);\n state.value = value;\n };\n\n return {\n signal: state,\n setLocal,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class AppOfflineError extends Error {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
export declare function isAppOfflineError(error: unknown): error is AppOfflineError;
|
|
5
|
+
export declare function getAppOnlineStatus(): boolean;
|
|
6
|
+
export declare function subscribeToAppNetworkStatus(onStoreChange: () => void): () => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/network/appNetworkStatus.ts
|
|
2
|
+
var e = class extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("The application is offline."), this.name = "AppOfflineError";
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
function t(t) {
|
|
8
|
+
return t instanceof e || t instanceof Error && t.name === "AppOfflineError";
|
|
9
|
+
}
|
|
10
|
+
function n() {
|
|
11
|
+
return typeof navigator > "u" || navigator.onLine;
|
|
12
|
+
}
|
|
13
|
+
function r(e) {
|
|
14
|
+
return typeof window > "u" ? () => void 0 : (window.addEventListener("online", e), window.addEventListener("offline", e), () => {
|
|
15
|
+
window.removeEventListener("online", e), window.removeEventListener("offline", e);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { e as AppOfflineError, n as getAppOnlineStatus, t as isAppOfflineError, r as subscribeToAppNetworkStatus };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=appNetworkStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appNetworkStatus.js","names":[],"sources":["../../src/network/appNetworkStatus.ts"],"sourcesContent":["export class AppOfflineError extends Error {\n constructor() {\n super(\"The application is offline.\");\n this.name = \"AppOfflineError\";\n }\n}\n\nexport function isAppOfflineError(error: unknown): error is AppOfflineError {\n return error instanceof AppOfflineError || (error instanceof Error && error.name === \"AppOfflineError\");\n}\n\nexport function getAppOnlineStatus(): boolean {\n return typeof navigator === \"undefined\" ? true : navigator.onLine;\n}\n\nexport function subscribeToAppNetworkStatus(onStoreChange: () => void): () => void {\n if (typeof window === \"undefined\") {\n return () => undefined;\n }\n\n window.addEventListener(\"online\", onStoreChange);\n window.addEventListener(\"offline\", onStoreChange);\n\n return () => {\n window.removeEventListener(\"online\", onStoreChange);\n window.removeEventListener(\"offline\", onStoreChange);\n };\n}\n"],"mappings":";AAAA,IAAa,IAAb,cAAqC,MAAM;CACzC,cAAc;EAEZ,AADA,MAAM,6BAA6B,GACnC,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,EAAkB,GAA0C;CAC1E,OAAO,aAAiB,KAAoB,aAAiB,SAAS,EAAM,SAAS;AACvF;AAEA,SAAgB,IAA8B;CAC5C,OAAO,OAAO,YAAc,OAAqB,UAAU;AAC7D;AAEA,SAAgB,EAA4B,GAAuC;CAQjF,OAPI,OAAO,SAAW,YACP,KAAA,KAGf,OAAO,iBAAiB,UAAU,CAAa,GAC/C,OAAO,iBAAiB,WAAW,CAAa,SAEnC;EAEX,AADA,OAAO,oBAAoB,UAAU,CAAa,GAClD,OAAO,oBAAoB,WAAW,CAAa;CACrD;AACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ConnectivitySnapshot = {
|
|
2
|
+
online: boolean;
|
|
3
|
+
heartbeatEnabled: boolean;
|
|
4
|
+
heartbeatConnected: boolean;
|
|
5
|
+
syncInProgress: boolean;
|
|
6
|
+
lastHeartbeatAt: number | null;
|
|
7
|
+
};
|
|
8
|
+
export type ConnectivityRuntimeAdapter = {
|
|
9
|
+
readBrowserOnline: () => boolean;
|
|
10
|
+
subscribeBrowserOnline: (listener: (online: boolean) => void) => () => void;
|
|
11
|
+
scheduleRepeating: (callback: () => void, intervalMs: number) => () => void;
|
|
12
|
+
};
|
|
13
|
+
export type ConnectivityStateOptions = {
|
|
14
|
+
initialBrowserOnline: boolean;
|
|
15
|
+
heartbeatStaleAfterMs: number;
|
|
16
|
+
heartbeatBootstrapAssumeOnlineMs: number;
|
|
17
|
+
runtimeIntervalMs?: number;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
};
|
|
20
|
+
export declare function createConnectivityState({ initialBrowserOnline, heartbeatStaleAfterMs, heartbeatBootstrapAssumeOnlineMs, runtimeIntervalMs, now, }: ConnectivityStateOptions): {
|
|
21
|
+
sigOnline: import('@preact/signals-core').ReadonlySignal<boolean>;
|
|
22
|
+
sigSnapshot: import('@preact/signals-core').ReadonlySignal<ConnectivitySnapshot>;
|
|
23
|
+
sigReconnectRequestTick: import('@preact/signals-core').Signal<number>;
|
|
24
|
+
setHeartbeatEnabled: (enabled: boolean) => void;
|
|
25
|
+
markHeartbeatConnected: () => void;
|
|
26
|
+
markHeartbeatDisconnected: () => void;
|
|
27
|
+
markHeartbeatReceived: (syncInProgress: boolean) => void;
|
|
28
|
+
markBackendUnavailable: () => void;
|
|
29
|
+
markBackendAvailable: () => void;
|
|
30
|
+
startRuntime: (adapter: ConnectivityRuntimeAdapter) => () => void;
|
|
31
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
2
|
+
type Storage<TData extends Record<string, unknown>> = {
|
|
3
|
+
get<TKey extends keyof TData>(key: TKey): TData[TKey];
|
|
4
|
+
set<TKey extends keyof TData>(key: TKey, value: TData[TKey]): void;
|
|
5
|
+
};
|
|
6
|
+
export type PersistentSignal<TData extends Record<string, unknown>, TKey extends keyof TData> = {
|
|
7
|
+
signal: Signal<TData[TKey]>;
|
|
8
|
+
setLocal: (value: TData[TKey]) => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function createPersistentSignal<TData extends Record<string, unknown>, TKey extends keyof TData>(storage: Storage<TData>, key: TKey): PersistentSignal<TData, TKey>;
|
|
11
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vireocodedev/infrastructure",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React-free browser infrastructure primitives for validated HTTP transport, connectivity, persistence, and execution-mode-aware services.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|