@ecosy/core 0.1.0 → 0.2.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.
- package/README.md +40 -198
- package/dist/http.d.ts +176 -0
- package/dist/http.js +1 -0
- package/dist/http.mjs +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/logger.d.ts +66 -0
- package/dist/logger.js +1 -0
- package/dist/logger.mjs +1 -0
- package/dist/searchify.d.ts +35 -0
- package/dist/searchify.js +1 -0
- package/dist/searchify.mjs +1 -0
- package/dist/serialize.d.ts +80 -0
- package/dist/serialize.js +1 -0
- package/dist/serialize.mjs +1 -0
- package/dist/slugify.d.ts +34 -0
- package/dist/slugify.js +1 -0
- package/dist/slugify.mjs +1 -0
- package/dist/syhemo.d.ts +102 -0
- package/dist/syhemo.js +1 -0
- package/dist/syhemo.mjs +1 -0
- package/dist/types/built-in.d.ts +22 -1
- package/dist/utilities/defer.d.ts +53 -0
- package/dist/utilities/defer.js +1 -0
- package/dist/utilities/defer.mjs +1 -0
- package/dist/utilities/filelist.d.ts +25 -0
- package/dist/utilities/filelist.js +1 -0
- package/dist/utilities/filelist.mjs +1 -0
- package/dist/utilities/flatten.d.ts +57 -0
- package/dist/utilities/flatten.js +1 -0
- package/dist/utilities/flatten.mjs +1 -0
- package/dist/utilities/get.d.ts +21 -0
- package/dist/utilities/get.js +1 -0
- package/dist/utilities/get.mjs +1 -0
- package/dist/utilities/index.d.ts +6 -0
- package/dist/utilities/index.js +1 -1
- package/dist/utilities/index.mjs +1 -1
- package/dist/utilities/object-to-formdata.d.ts +26 -0
- package/dist/utilities/object-to-formdata.js +1 -0
- package/dist/utilities/object-to-formdata.mjs +1 -0
- package/dist/utilities/object.js +1 -1
- package/dist/utilities/object.mjs +1 -1
- package/dist/utilities/pascal-to-kebab.d.ts +15 -0
- package/dist/utilities/pascal-to-kebab.js +1 -0
- package/dist/utilities/pascal-to-kebab.mjs +1 -0
- package/package.json +39 -3
package/README.md
CHANGED
|
@@ -1,234 +1,76 @@
|
|
|
1
1
|
# @ecosy/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modular, tree-shakable collection of essential utilities, serialization primitives, and event-driven patterns for modern TypeScript applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @ecosy/core
|
|
9
|
-
# or
|
|
10
8
|
yarn add @ecosy/core
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
##
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### Subpath Imports
|
|
14
14
|
|
|
15
15
|
| Entry point | Description |
|
|
16
16
|
|--|--|
|
|
17
|
-
| `@ecosy/core` | Re-exports
|
|
17
|
+
| `@ecosy/core` | Re-exports all modules |
|
|
18
18
|
| `@ecosy/core/types` | TypeScript type utilities |
|
|
19
19
|
| `@ecosy/core/utilities` | Runtime utility functions |
|
|
20
20
|
| `@ecosy/core/subscriber` | Pub/sub event emitter with state |
|
|
21
|
+
| `@ecosy/core/http` | HTTP client with interceptors and upload |
|
|
22
|
+
| `@ecosy/core/logger` | Structured logger with log levels |
|
|
23
|
+
| `@ecosy/core/syhemo` | System health monitor |
|
|
24
|
+
| `@ecosy/core/serialize` | Serialization engine (JSON, URL, queryString) |
|
|
25
|
+
| `@ecosy/core/slugify` | Unicode-safe string slugifier |
|
|
26
|
+
| `@ecosy/core/searchify` | Diacritic-insensitive fuzzy search |
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Types
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import type {
|
|
28
|
-
primitive,
|
|
29
|
-
LiteralObject,
|
|
30
|
-
LiteralFunction,
|
|
31
|
-
Objectable,
|
|
32
|
-
Freezable,
|
|
33
|
-
PartialLiteral,
|
|
34
|
-
ToString,
|
|
35
|
-
Promisable,
|
|
36
|
-
AtomicObject,
|
|
37
|
-
} from "@ecosy/core/types";
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
| Type | Description |
|
|
41
|
-
|--|--|
|
|
42
|
-
| `primitive` | `string \| number \| boolean \| bigint \| symbol \| undefined \| null` |
|
|
43
|
-
| `LiteralObject` | Plain object type (`Record<PropertyKey, unknown> \| object`) |
|
|
44
|
-
| `LiteralFunction<R, A>` | Generic function type `(...args: A) => R` |
|
|
45
|
-
| `Objectable` | `LiteralObject \| Array \| LiteralFunction` |
|
|
46
|
-
| `Freezable<T>` | Recursively readonly version of `T` |
|
|
47
|
-
| `PartialLiteral<T>` | Deep partial that respects built-in types (Map, Set, Promise, etc.) |
|
|
48
|
-
| `ToString<T>` | Converts type to its string literal representation |
|
|
49
|
-
| `Promisable<V>` | `V \| Promise<V>` |
|
|
50
|
-
| `AtomicObject<K, V>` | `{ [K]: V }` |
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Utilities
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
import {
|
|
58
|
-
clone,
|
|
59
|
-
freeze,
|
|
60
|
-
isEqual,
|
|
61
|
-
isFunction,
|
|
62
|
-
isObject,
|
|
63
|
-
isLiteralObject,
|
|
64
|
-
isComplexObject,
|
|
65
|
-
isObjectable,
|
|
66
|
-
hasOwnProperty,
|
|
67
|
-
merge,
|
|
68
|
-
toString,
|
|
69
|
-
ucfirst,
|
|
70
|
-
} from "@ecosy/core/utilities";
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### `clone<T>(data: T): T`
|
|
74
|
-
|
|
75
|
-
Deep clones a value. Handles circular references, Date, RegExp, Map, Set, ArrayBuffer, TypedArrays, arrays, and plain objects. Skips non-cloneable types (Error, Promise, WeakMap, DOM nodes, etc.).
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
const original = { a: 1, b: { c: [2, 3] } };
|
|
79
|
-
const cloned = clone(original);
|
|
80
|
-
cloned.b.c.push(4);
|
|
81
|
-
original.b.c.length; // 3 — unaffected
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### `freeze<T>(data: T): Freezable<T>`
|
|
85
|
-
|
|
86
|
-
Deep freezes a value by cloning first, then recursively calling `Object.freeze`.
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
const frozen = freeze({ a: { b: 1 } });
|
|
90
|
-
frozen.a.b = 2; // throws in strict mode
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### `isEqual(a: unknown, b: unknown): boolean`
|
|
28
|
+
### Types
|
|
94
29
|
|
|
95
|
-
Deep
|
|
30
|
+
Deep utility types for TypeScript — `Freezable<T>`, `PartialLiteral<T>`, `Promisable<V>`, `ToString<T>`, and more.
|
|
96
31
|
|
|
97
|
-
|
|
98
|
-
isEqual({ a: [1, 2] }, { a: [1, 2] }); // true
|
|
99
|
-
isEqual(new Date(0), new Date(0)); // true
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### `merge<T>(source, target, cloneDeep?): T`
|
|
103
|
-
|
|
104
|
-
Deep merges `target` into `source`. Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are rejected.
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
merge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
|
|
108
|
-
// { a: 1, b: { c: 2, d: 3 } }
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### `isFunction(value): value is LiteralFunction`
|
|
112
|
-
|
|
113
|
-
Checks if a value is a function (sync, async, or generator).
|
|
32
|
+
### Utilities
|
|
114
33
|
|
|
115
|
-
|
|
34
|
+
- **`clone`** — Deep clone with circular reference handling
|
|
35
|
+
- **`freeze`** — Deep freeze via clone + `Object.freeze`
|
|
36
|
+
- **`isEqual`** — Structural deep equality (Date, Map, Set, TypedArrays)
|
|
37
|
+
- **`merge`** — Deep merge with prototype pollution protection
|
|
38
|
+
- **`flatten`** / **`flattenToArray`** — Object flattening
|
|
39
|
+
- **`get`** — Safe deep path resolution (`"a.b[0].c"`)
|
|
40
|
+
- **`defer`** / **`deferAsync`** — rAF + setTimeout scheduling with cancel
|
|
41
|
+
- Type guards: `isFunction`, `isObject`, `isLiteralObject`, `isComplexObject`, `isObjectable`, `hasOwnProperty`
|
|
116
42
|
|
|
117
|
-
|
|
43
|
+
### Subscriber
|
|
118
44
|
|
|
119
|
-
|
|
45
|
+
Pub/sub event emitter with built-in state management, async once, and typed wiring.
|
|
120
46
|
|
|
121
|
-
|
|
47
|
+
### Http
|
|
122
48
|
|
|
123
|
-
|
|
49
|
+
Configurable HTTP client with request/response interceptors, auth token injection, URL interpolation via Serialize, and XHR-based upload with progress tracking.
|
|
124
50
|
|
|
125
|
-
|
|
51
|
+
### Serialize
|
|
126
52
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Type-safe `Object.prototype.hasOwnProperty.call()`.
|
|
134
|
-
|
|
135
|
-
### `toString(value): string`
|
|
136
|
-
|
|
137
|
-
Returns the internal `[[Class]]` tag via `Object.prototype.toString`.
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
toString([]); // "[object Array]"
|
|
141
|
-
toString(null); // "[object Null]"
|
|
142
|
-
```
|
|
53
|
+
Centralized serialization engine:
|
|
54
|
+
- **`Serialize.Primitive`** — Type guards and deep normalization (BigInt, Date, undefined stripping)
|
|
55
|
+
- **`Serialize.JSON`** — Safe stringify/parse that never throws
|
|
56
|
+
- **`Serialize.URL`** — Robust encode/decode with `:param` URL building
|
|
57
|
+
- **`Serialize.queryString`** — Advanced parse/stringify (bracket, index, comma formats)
|
|
143
58
|
|
|
144
|
-
###
|
|
59
|
+
### Slugify & Searchify
|
|
145
60
|
|
|
146
|
-
|
|
61
|
+
- **`slugify`** — Unicode-safe slug generation with custom transformer map
|
|
62
|
+
- **`searchify`** — Diacritic-insensitive fuzzy search using sliding window algorithm
|
|
147
63
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
## Subscriber
|
|
151
|
-
|
|
152
|
-
A pub/sub event emitter with built-in state management.
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
import { Subscriber } from "@ecosy/core/subscriber";
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Basic usage
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
const sub = new Subscriber({ count: 0 });
|
|
162
|
-
|
|
163
|
-
// Subscribe to state changes
|
|
164
|
-
const unsub = sub.onStateChange((state) => {
|
|
165
|
-
console.log("Count:", state.count);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Update state
|
|
169
|
-
sub.setState({ count: 1 }); // logs "Count: 1"
|
|
170
|
-
|
|
171
|
-
// Cleanup
|
|
172
|
-
unsub();
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### Custom channels
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
sub.subscribe("user:login", (user) => {
|
|
179
|
-
console.log("Logged in:", user.name);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
sub.dispatch("user:login", { name: "Alice" });
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Async once
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
const payload = await sub.subscribeAsyncOnce("data:ready");
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
> [!WARNING]
|
|
192
|
-
> If the channel is never dispatched and no `AbortSignal` is provided, the returned Promise will never resolve, causing a **memory leak**. Always pass an `AbortSignal` or ensure the channel will eventually be dispatched.
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
const controller = new AbortController();
|
|
196
|
-
setTimeout(() => controller.abort(), 5000); // timeout after 5s
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const payload = await sub.subscribeAsyncOnce("data:ready", undefined, controller.signal);
|
|
200
|
-
} catch {
|
|
201
|
-
console.log("Timed out or cancelled");
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### Wiring events
|
|
206
|
-
|
|
207
|
-
```ts
|
|
208
|
-
const events = {
|
|
209
|
-
auth: {
|
|
210
|
-
login: "$auth:login",
|
|
211
|
-
logout: "$auth:logout",
|
|
212
|
-
},
|
|
213
|
-
} as const;
|
|
214
|
-
|
|
215
|
-
const wired = Subscriber.wire(sub, events);
|
|
216
|
-
|
|
217
|
-
// Dispatch
|
|
218
|
-
wired.auth.login({ user: "Alice" });
|
|
219
|
-
|
|
220
|
-
// Listen
|
|
221
|
-
wired.auth.onLogin((payload) => {
|
|
222
|
-
console.log(payload.user);
|
|
223
|
-
});
|
|
224
|
-
```
|
|
64
|
+
## Documentation
|
|
225
65
|
|
|
226
|
-
|
|
66
|
+
Full API reference and guides: **[docs.ecosy.io](https://docs.ecosy.io)**
|
|
227
67
|
|
|
228
|
-
## Related
|
|
68
|
+
## Related Packages
|
|
229
69
|
|
|
230
70
|
| Package | Description |
|
|
231
71
|
|--|--|
|
|
72
|
+
| [`@ecosy/datekit`](https://github.com/material-atomic/ecosy-datekit) | Headless date utilities (Dateify, Dayify, Monthify, Yearify) |
|
|
73
|
+
| [`@ecosy/mailer`](https://github.com/material-atomic/ecosy-mailer) | Email engine with template formatting, retry, and rate limiting |
|
|
232
74
|
| [`@ecosy/store`](https://github.com/material-atomic/ecosy-store) | State management with slices and reducers |
|
|
233
75
|
| [`@ecosy/react`](https://github.com/material-atomic/ecosy-react) | React hooks for `@ecosy/store` |
|
|
234
76
|
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { type FileListLike } from "@ecosy/core/utilities";
|
|
2
|
+
/** Default base URL for HTTP requests, sourced from `API_URL` env variable. */
|
|
3
|
+
export declare const DEFAULT_BASE_URL: string;
|
|
4
|
+
/** Authentication token storage key, sourced from `API_AUTH_TOKEN_KEY` env variable. */
|
|
5
|
+
export declare const API_AUTH_TOKEN_KEY: string | undefined;
|
|
6
|
+
/** Authentication header name, sourced from `API_AUTH_HEADER_KEY` env variable. */
|
|
7
|
+
export declare const API_AUTH_HEADER_KEY: string | undefined;
|
|
8
|
+
/** Authentication header type prefix (e.g. "Bearer"), sourced from `API_AUTH_HEADER_TYPE` env variable. */
|
|
9
|
+
export declare const API_AUTH_HEADER_TYPE: string | undefined;
|
|
10
|
+
/** Supported HTTP methods. */
|
|
11
|
+
export declare enum HttpMethod {
|
|
12
|
+
GET = "GET",
|
|
13
|
+
POST = "POST",
|
|
14
|
+
PUT = "PUT",
|
|
15
|
+
DELETE = "DELETE",
|
|
16
|
+
PATCH = "PATCH",
|
|
17
|
+
HEAD = "HEAD",
|
|
18
|
+
OPTIONS = "OPTIONS"
|
|
19
|
+
}
|
|
20
|
+
/** Accepted query parameter formats for HTTP requests. */
|
|
21
|
+
export type HttpQuery = string | Record<string, string | number | boolean | undefined> | Array<[string, string | number | boolean | undefined]> | URLSearchParams;
|
|
22
|
+
/** Configuration object for an HTTP request. */
|
|
23
|
+
export interface HttpRequest<Body = unknown, Params = Record<string, unknown>, Query = HttpQuery, Headers = Record<string, string>> {
|
|
24
|
+
method: HttpMethod;
|
|
25
|
+
url: string;
|
|
26
|
+
headers?: Headers;
|
|
27
|
+
body?: Body;
|
|
28
|
+
query?: Query;
|
|
29
|
+
params?: Params;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
/** Storage adapter interface for reading/writing auth tokens (e.g. `localStorage`). */
|
|
33
|
+
export interface HttpStorage {
|
|
34
|
+
getItem(key: string): string | null;
|
|
35
|
+
setItem(key: string, value: string): void;
|
|
36
|
+
removeItem(key: string, options?: any): void;
|
|
37
|
+
}
|
|
38
|
+
/** Interceptor that can modify a request before it is sent. */
|
|
39
|
+
export type HttpInterceptorRequest = (options: HttpRequest) => Promise<HttpRequest> | HttpRequest;
|
|
40
|
+
/** Interceptor that can modify a `Response` after it is received. */
|
|
41
|
+
export type HttpInterceptorResponse = (response: Response) => Promise<Response> | Response;
|
|
42
|
+
/** Interceptor that transforms the parsed response data. */
|
|
43
|
+
export type HttpInterceptorTransform = (data: any) => Promise<any> | any;
|
|
44
|
+
/** Interceptor that handles errors from requests. */
|
|
45
|
+
export type HttpInterceptorError = (error: unknown) => Promise<any> | any;
|
|
46
|
+
/** Tuple type for registering interceptors by phase. */
|
|
47
|
+
export type HttpInterceptorParameters = ["request", HttpInterceptorRequest] | ["response", HttpInterceptorResponse] | ["transform", HttpInterceptorTransform] | ["error", HttpInterceptorError];
|
|
48
|
+
/** Normalized response returned by all `Http` methods. */
|
|
49
|
+
export interface HttpResponse<T = unknown, E = unknown> {
|
|
50
|
+
success: boolean;
|
|
51
|
+
data: T;
|
|
52
|
+
status: number;
|
|
53
|
+
statusText: string;
|
|
54
|
+
headers: Record<string, string>;
|
|
55
|
+
error: E | null;
|
|
56
|
+
}
|
|
57
|
+
/** Upload progress snapshot. */
|
|
58
|
+
export interface HttpProgress {
|
|
59
|
+
loaded: number;
|
|
60
|
+
total: number;
|
|
61
|
+
percentage: number;
|
|
62
|
+
}
|
|
63
|
+
/** Options for file upload, including progress tracking. */
|
|
64
|
+
export interface HttpUploadOptions extends Pick<HttpRequest, "headers" | "params" | "signal" | "query"> {
|
|
65
|
+
onProgress?: (progress: HttpProgress) => void;
|
|
66
|
+
name: string;
|
|
67
|
+
body?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
/** Options for creating an HTTP factory via {@link Http.createFactory}. */
|
|
70
|
+
export interface HttpFactoryOptions {
|
|
71
|
+
baseURL?: string;
|
|
72
|
+
http?: Http;
|
|
73
|
+
storage?: HttpStorage | (() => Promise<HttpStorage> | HttpStorage);
|
|
74
|
+
endpoint?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
75
|
+
}
|
|
76
|
+
/** A factory-generated endpoint descriptor with a key and an async `fn`. */
|
|
77
|
+
export interface HttpFactory<T = unknown, Args extends any[] = [], E = unknown> {
|
|
78
|
+
key: string;
|
|
79
|
+
fn: (...args: Args) => Promise<HttpResponse<T, E>>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* HTTP client with interceptors, auth token management, file uploads,
|
|
83
|
+
* and a factory pattern for endpoint generation.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const http = new Http("https://api.example.com");
|
|
88
|
+
*
|
|
89
|
+
* const { data } = await http.get<User[]>("/users");
|
|
90
|
+
* await http.post<User>("/users", { name: "Alice" });
|
|
91
|
+
* await http.upload<Media>("/upload", file, { name: "avatar" });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare class Http {
|
|
95
|
+
private readonly baseURL;
|
|
96
|
+
static readonly method: typeof HttpMethod;
|
|
97
|
+
static authTokenKey: string;
|
|
98
|
+
static authHeaderKey: string;
|
|
99
|
+
static authHeaderType: string;
|
|
100
|
+
static authDetectToken: string[];
|
|
101
|
+
static extraHeaders: Record<string, string> | null;
|
|
102
|
+
static storage: HttpStorage | null;
|
|
103
|
+
private defaultHeaders;
|
|
104
|
+
readonly method: typeof HttpMethod;
|
|
105
|
+
private storage;
|
|
106
|
+
private static interceptors;
|
|
107
|
+
private interceptors;
|
|
108
|
+
constructor(baseURL?: string);
|
|
109
|
+
/** Register a global interceptor (applies to all `Http` instances). */
|
|
110
|
+
static on(...params: HttpInterceptorParameters): void;
|
|
111
|
+
/** Remove a global interceptor. */
|
|
112
|
+
static off(...params: HttpInterceptorParameters): void;
|
|
113
|
+
/** Set the storage adapter for this instance (used to read auth tokens). */
|
|
114
|
+
setStorage(storage: HttpStorage): void;
|
|
115
|
+
/** Type guard that checks whether a value is a valid {@link HttpQuery}. */
|
|
116
|
+
isValidQuery(query: unknown): query is HttpQuery;
|
|
117
|
+
getQuery<Params extends Record<string, unknown>>(options: HttpRequest<unknown, Params>): string;
|
|
118
|
+
/** Type guard that checks whether a body is a `FormData` instance. */
|
|
119
|
+
isFormData(body: unknown): body is FormData;
|
|
120
|
+
/** Merge additional default headers into this instance. */
|
|
121
|
+
addHeaders(headers: Record<string, string>): void;
|
|
122
|
+
/** Register an instance-level interceptor. */
|
|
123
|
+
on(...params: HttpInterceptorParameters): this;
|
|
124
|
+
/** Remove an instance-level interceptor. */
|
|
125
|
+
off(...params: HttpInterceptorParameters): this;
|
|
126
|
+
/** Read the auth token from storage and format it with the configured header type. */
|
|
127
|
+
getToken(): {
|
|
128
|
+
key: string;
|
|
129
|
+
value: string;
|
|
130
|
+
};
|
|
131
|
+
/** Build the final headers object, injecting auth token and Content-Type. */
|
|
132
|
+
getHeaders(headers?: Record<string, string>, isFormData?: boolean): {
|
|
133
|
+
[x: string]: string;
|
|
134
|
+
};
|
|
135
|
+
/** Build the full URL from base URL, path, query string, and path params. */
|
|
136
|
+
getURL(options: HttpRequest): string;
|
|
137
|
+
/** Serialize the request body (JSON or FormData). Returns `undefined` for bodyless methods. */
|
|
138
|
+
getBody(options: HttpRequest): string | FormData | undefined;
|
|
139
|
+
/** Execute a full HTTP request with all interceptors applied. */
|
|
140
|
+
request<T = unknown, E = unknown>(options: HttpRequest): Promise<HttpResponse<T, E>>;
|
|
141
|
+
/** Perform a GET request. */
|
|
142
|
+
get<T = unknown, Query = Record<string, unknown>, E = unknown>(url: string, query?: Query, options?: Pick<HttpRequest, "headers" | "params" | "signal">): Promise<HttpResponse<T, E>>;
|
|
143
|
+
/** Perform a POST request. */
|
|
144
|
+
post<T = unknown, Body = unknown, E = unknown>(url: string, body?: Body, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
145
|
+
/** Perform a PUT request. */
|
|
146
|
+
put<T = unknown, Body = unknown, E = unknown>(url: string, body?: Body, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
147
|
+
/** Perform a PATCH request. */
|
|
148
|
+
patch<T = unknown, Body = unknown, E = unknown>(url: string, body?: Body, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
149
|
+
/** Perform a DELETE request. */
|
|
150
|
+
delete<T = unknown, E = unknown>(url: string, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
151
|
+
/** Perform a HEAD request. */
|
|
152
|
+
head<T = unknown, E = unknown>(url: string, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
153
|
+
/** Perform an OPTIONS request. */
|
|
154
|
+
options<T = unknown, E = unknown>(url: string, options?: Pick<HttpRequest, "headers" | "params" | "signal" | "query">): Promise<HttpResponse<T, E>>;
|
|
155
|
+
/**
|
|
156
|
+
* Upload one or more files via POST.
|
|
157
|
+
* Falls back to `XMLHttpRequest` when `onProgress` is provided for progress tracking.
|
|
158
|
+
*/
|
|
159
|
+
upload<T = unknown, E = unknown>(url: string, file: File | File[] | FileListLike, options?: HttpUploadOptions): Promise<HttpResponse<T, E>>;
|
|
160
|
+
/**
|
|
161
|
+
* Creates a factory function that generates typed endpoint descriptors.
|
|
162
|
+
* Each descriptor has a `key` and an async `fn` that performs the HTTP call.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const api = Http.createFactory({
|
|
167
|
+
* baseURL: "https://api.example.com",
|
|
168
|
+
* endpoint: { users: { list: "/users", create: "/users" } },
|
|
169
|
+
* });
|
|
170
|
+
*
|
|
171
|
+
* const getUsers = api<User[]>("users.list");
|
|
172
|
+
* const { data } = await getUsers.fn();
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
static createFactory(options?: HttpFactoryOptions): <T, Args extends any[] = [], E = unknown>(key: string, method?: HttpMethod | "UPLOAD") => HttpFactory<T, Args, E>;
|
|
176
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";require("./utilities/clone.js");var t=require("./utilities/filelist.js"),e=require("./utilities/flatten.js"),s=require("./utilities/object-to-formdata.js"),r=require("./serialize.js");const o=process.env.API_URL||"/",n=process.env.API_AUTH_TOKEN_KEY,a=process.env.API_AUTH_HEADER_KEY,i=process.env.API_AUTH_HEADER_TYPE;var c;exports.HttpMethod=void 0,(c=exports.HttpMethod||(exports.HttpMethod={})).GET="GET",c.POST="POST",c.PUT="PUT",c.DELETE="DELETE",c.PATCH="PATCH",c.HEAD="HEAD",c.OPTIONS="OPTIONS";class p{constructor(t=o){this.baseURL=t,this.defaultHeaders={"Content-Type":"application/json"},this.method=p.method,this.storage=p.storage,this.interceptors={request:[],response:[],transform:[],error:[]}}static on(...t){const[e,s]=t,r=p.interceptors[e];r.includes(s)||r.push(s),p.interceptors[e]=r}static off(...t){const[e,s]=t,r=p.interceptors[e];p.interceptors[e]=r.filter(t=>t!==s)}setStorage(t){this.storage=t}isValidQuery(t){return"string"==typeof t||t instanceof URLSearchParams||(Array.isArray(t)?t.every(t=>Array.isArray(t)&&2===t.length&&"string"==typeof t[0]&&r.Serialize.Primitive.isPrimitive(t[1])):"object"==typeof t&&null!==t&&Object.values(t).every(t=>r.Serialize.Primitive.isPrimitive(t)))}getQuery(t){const{method:e,body:s,query:o}=t,n=e===exports.HttpMethod.GET&&this.isValidQuery(s)?s:o;return n&&"object"==typeof n?n instanceof URLSearchParams?n.toString():r.Serialize.queryString.stringify(n,{skipNull:!0,skipEmptyString:!0}):"string"==typeof n?n:""}isFormData(t){return t instanceof FormData}addHeaders(t){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),t)}on(...t){const[e,s]=t,r=this.interceptors[e];return r.includes(s)||r.push(s),this.interceptors[e]=r,this}off(...t){const[e,s]=t,r=this.interceptors[e];return this.interceptors[e]=r.filter(t=>t!==s),this}getToken(){const t=n||p.authTokenKey,e=a||p.authHeaderKey,s=i||p.authHeaderType;if(!t||!e)return{key:e,value:""};let r="";this.storage&&(r=this.storage.getItem(t)||"");const o={key:e,value:""};return r&&(o.value=r,s&&(o.value=`${s} ${r}`)),o}getHeaders(t={},e){const s=this.getToken();s.key in t&&t[s.key]||(t[s.key]=s.value),e&&"Content-Type"in t?delete t["Content-Type"]:t["Content-Type"]=t["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const r=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),t),p.extraHeaders||{});return p.extraHeaders=null,r}getURL(t){const{url:e,params:s={}}=t,o=this.getQuery(t);let n=e;if(e)if(e.match(/^https?:\/\//))n=e;else{n=`${this.baseURL.replace(/\/+$/,"")}/${e.replace(/^\/+/,"")}`}if(o){const t=n.includes("?")?"&":"?";n+=`${t}${o}`}return r.Serialize.interpolate(n,s)}getBody(t){const{method:e,body:s}=t;if(e!==exports.HttpMethod.GET&&e!==exports.HttpMethod.HEAD&&e!==exports.HttpMethod.OPTIONS&&e!==exports.HttpMethod.DELETE)return e!==exports.HttpMethod.POST&&e!==exports.HttpMethod.PUT&&e!==exports.HttpMethod.PATCH||!this.isFormData(s)?void 0!==s?JSON.stringify(s):void 0:s}async request(t){const{method:e=exports.HttpMethod.GET}=t;try{let s=Object.assign({},t);const r=[...p.interceptors.request,...this.interceptors.request];for(const t of r)s=await t(s);const o=this.getURL(s),n=this.getHeaders(s.headers||{});this.isFormData(s.body)&&"Content-Type"in n&&delete n["Content-Type"];const a=await fetch(o,{method:s.method||e,headers:n,body:this.getBody(s),signal:s.signal}),i=a.headers.get("Content-Type")||"";let c=null;c=i.includes("application/json")?await a.json():await a.text();const u=[...p.interceptors.transform,...this.interceptors.transform];let d=c;for(const t of u)d=await t(d);const l=[...p.interceptors.response,...this.interceptors.response];let h=a;for(const t of l)h=await t(h);const f={};h.headers.forEach((t,e)=>{f[e]=t});const T=h.ok;let y=null;if(!T){y=c.error||c;const t=[...p.interceptors.error,...this.interceptors.error];for(const e of t)y=await e(y)}return{data:d,success:T,error:T?null:y,status:h.status,statusText:h.statusText,headers:f}}catch(t){let e=t instanceof Error?t:new Error(String(t));const s=[...p.interceptors.error,...this.interceptors.error];for(const t of s)e=await t(e);return{data:null,status:0,statusText:"Error",headers:{},error:e,success:!1}}}get(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:exports.HttpMethod.GET,url:t,query:e}))}post(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:exports.HttpMethod.POST,url:t,body:e}))}put(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:exports.HttpMethod.PUT,url:t,body:e}))}patch(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:exports.HttpMethod.PATCH,url:t,body:e}))}delete(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.DELETE,url:t}))}head(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.HEAD,url:t}))}options(t,e){return this.request(Object.assign(Object.assign({},e),{method:exports.HttpMethod.OPTIONS,url:t}))}upload(r,o,n){const a=(null==n?void 0:n.body)?s.objectToFormData(n.body):new FormData;let i=(null==n?void 0:n.name)||"file";if(Array.isArray(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),o.forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):t.isFileList(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),Array.from(o).forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):a.append(i,o,o.name),null==n?void 0:n.body){const t=e.flatten(n.body);Object.entries(t).forEach(([t,e])=>{null!=e&&a.append(t,String(e))})}return(null==n?void 0:n.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async t=>{try{let e=Object.assign(Object.assign({},n),{method:exports.HttpMethod.POST,url:r,body:a});const s=[...p.interceptors.request,...this.interceptors.request];for(const t of s)e=await t(e);const o=this.getURL(e),i=this.getHeaders(e.headers||{},!0),c=new XMLHttpRequest;c.upload.addEventListener("progress",t=>{var e;if(t.lengthComputable){const s=Math.round(t.loaded/t.total*100);null===(e=n.onProgress)||void 0===e||e.call(n,{loaded:t.loaded,total:t.total,percentage:s})}}),c.addEventListener("load",async()=>{try{const e=c.getResponseHeader("Content-Type")||"";let s=null;s=e.includes("application/json")?JSON.parse(c.responseText):c.responseText;const r=[...p.interceptors.transform,...this.interceptors.transform];let o=s;for(const t of r)o=await t(o);const n=[...p.interceptors.response,...this.interceptors.response];let a=new Response(c.responseText,{status:c.status,statusText:c.statusText,headers:i});for(const t of n)a=await t(a);const u={};a.headers.forEach((t,e)=>{u[e]=t});const d=a.ok;t({data:o,success:d,error:d?null:o,status:a.status,statusText:a.statusText,headers:u})}catch(e){const s=[...p.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}),c.addEventListener("error",e=>{const s=[...p.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}),c.addEventListener("abort",()=>{t({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),c.open(e.method||exports.HttpMethod.POST,o,!0),Object.entries(i).forEach(([t,e])=>{"content-type"!==(null==t?void 0:t.toLowerCase())&&e&&c.setRequestHeader(t,e)}),c.send(e.body)}catch(e){const s=[...p.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}):this.request({method:exports.HttpMethod.POST,url:r,body:a,headers:null==n?void 0:n.headers,params:null==n?void 0:n.params,signal:null==n?void 0:n.signal,query:null==n?void 0:n.query})}static createFactory(t={}){const s=t.http||new p(t.baseURL);return function(r,o){const n=e.flatten("function"==typeof t.endpoint?t.endpoint():t.endpoint||{})[r];return{key:r,fn:async(...e)=>{const r="function"==typeof t.storage?await t.storage():t.storage;switch(r&&s.setStorage(r),o){case exports.HttpMethod.POST:return await s.post(n,...e);case exports.HttpMethod.PUT:return await s.put(n,...e);case exports.HttpMethod.PATCH:return await s.patch(n,...e);case exports.HttpMethod.DELETE:return await s.delete(n,...e);case exports.HttpMethod.HEAD:return await s.head(n,...e);case exports.HttpMethod.OPTIONS:return await s.options(n,...e);case"UPLOAD":return await s.upload(n,...e);default:return await s.get(n,...e)}}}}}}p.method=exports.HttpMethod,p.authTokenKey="access_token",p.authHeaderKey="Authorization",p.authHeaderType="Bearer",p.authDetectToken=["localStorage","sessionStorage","cookie"],p.extraHeaders=null,p.storage=null,p.interceptors={request:[],response:[],transform:[],error:[]},exports.API_AUTH_HEADER_KEY=a,exports.API_AUTH_HEADER_TYPE=i,exports.API_AUTH_TOKEN_KEY=n,exports.DEFAULT_BASE_URL=o,exports.Http=p;
|
package/dist/http.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./utilities/clone.mjs";import{isFileList as t}from"./utilities/filelist.mjs";import{flatten as e}from"./utilities/flatten.mjs";import{objectToFormData as s}from"./utilities/object-to-formdata.mjs";import{Serialize as r}from"./serialize.mjs";const o=process.env.API_URL||"/",n=process.env.API_AUTH_TOKEN_KEY,a=process.env.API_AUTH_HEADER_KEY,i=process.env.API_AUTH_HEADER_TYPE;var c;!function(t){t.GET="GET",t.POST="POST",t.PUT="PUT",t.DELETE="DELETE",t.PATCH="PATCH",t.HEAD="HEAD",t.OPTIONS="OPTIONS"}(c||(c={}));class u{constructor(t=o){this.baseURL=t,this.defaultHeaders={"Content-Type":"application/json"},this.method=u.method,this.storage=u.storage,this.interceptors={request:[],response:[],transform:[],error:[]}}static on(...t){const[e,s]=t,r=u.interceptors[e];r.includes(s)||r.push(s),u.interceptors[e]=r}static off(...t){const[e,s]=t,r=u.interceptors[e];u.interceptors[e]=r.filter(t=>t!==s)}setStorage(t){this.storage=t}isValidQuery(t){return"string"==typeof t||t instanceof URLSearchParams||(Array.isArray(t)?t.every(t=>Array.isArray(t)&&2===t.length&&"string"==typeof t[0]&&r.Primitive.isPrimitive(t[1])):"object"==typeof t&&null!==t&&Object.values(t).every(t=>r.Primitive.isPrimitive(t)))}getQuery(t){const{method:e,body:s,query:o}=t,n=e===c.GET&&this.isValidQuery(s)?s:o;return n&&"object"==typeof n?n instanceof URLSearchParams?n.toString():r.queryString.stringify(n,{skipNull:!0,skipEmptyString:!0}):"string"==typeof n?n:""}isFormData(t){return t instanceof FormData}addHeaders(t){this.defaultHeaders=Object.assign(Object.assign({},this.defaultHeaders),t)}on(...t){const[e,s]=t,r=this.interceptors[e];return r.includes(s)||r.push(s),this.interceptors[e]=r,this}off(...t){const[e,s]=t,r=this.interceptors[e];return this.interceptors[e]=r.filter(t=>t!==s),this}getToken(){const t=n||u.authTokenKey,e=a||u.authHeaderKey,s=i||u.authHeaderType;if(!t||!e)return{key:e,value:""};let r="";this.storage&&(r=this.storage.getItem(t)||"");const o={key:e,value:""};return r&&(o.value=r,s&&(o.value=`${s} ${r}`)),o}getHeaders(t={},e){const s=this.getToken();s.key in t&&t[s.key]||(t[s.key]=s.value),e&&"Content-Type"in t?delete t["Content-Type"]:t["Content-Type"]=t["Content-Type"]||this.defaultHeaders["Content-Type"]||"application/json";const r=Object.assign(Object.assign(Object.assign({},this.defaultHeaders),t),u.extraHeaders||{});return u.extraHeaders=null,r}getURL(t){const{url:e,params:s={}}=t,o=this.getQuery(t);let n=e;if(e)if(e.match(/^https?:\/\//))n=e;else{n=`${this.baseURL.replace(/\/+$/,"")}/${e.replace(/^\/+/,"")}`}if(o){const t=n.includes("?")?"&":"?";n+=`${t}${o}`}return r.interpolate(n,s)}getBody(t){const{method:e,body:s}=t;if(e!==c.GET&&e!==c.HEAD&&e!==c.OPTIONS&&e!==c.DELETE)return e!==c.POST&&e!==c.PUT&&e!==c.PATCH||!this.isFormData(s)?void 0!==s?JSON.stringify(s):void 0:s}async request(t){const{method:e=c.GET}=t;try{let s=Object.assign({},t);const r=[...u.interceptors.request,...this.interceptors.request];for(const t of r)s=await t(s);const o=this.getURL(s),n=this.getHeaders(s.headers||{});this.isFormData(s.body)&&"Content-Type"in n&&delete n["Content-Type"];const a=await fetch(o,{method:s.method||e,headers:n,body:this.getBody(s),signal:s.signal}),i=a.headers.get("Content-Type")||"";let c=null;c=i.includes("application/json")?await a.json():await a.text();const l=[...u.interceptors.transform,...this.interceptors.transform];let d=c;for(const t of l)d=await t(d);const p=[...u.interceptors.response,...this.interceptors.response];let h=a;for(const t of p)h=await t(h);const f={};h.headers.forEach((t,e)=>{f[e]=t});const y=h.ok;let g=null;if(!y){g=c.error||c;const t=[...u.interceptors.error,...this.interceptors.error];for(const e of t)g=await e(g)}return{data:d,success:y,error:y?null:g,status:h.status,statusText:h.statusText,headers:f}}catch(t){let e=t instanceof Error?t:new Error(String(t));const s=[...u.interceptors.error,...this.interceptors.error];for(const t of s)e=await t(e);return{data:null,status:0,statusText:"Error",headers:{},error:e,success:!1}}}get(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:c.GET,url:t,query:e}))}post(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:c.POST,url:t,body:e}))}put(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:c.PUT,url:t,body:e}))}patch(t,e,s){return this.request(Object.assign(Object.assign({},s),{method:c.PATCH,url:t,body:e}))}delete(t,e){return this.request(Object.assign(Object.assign({},e),{method:c.DELETE,url:t}))}head(t,e){return this.request(Object.assign(Object.assign({},e),{method:c.HEAD,url:t}))}options(t,e){return this.request(Object.assign(Object.assign({},e),{method:c.OPTIONS,url:t}))}upload(r,o,n){const a=(null==n?void 0:n.body)?s(n.body):new FormData;let i=(null==n?void 0:n.name)||"file";if(Array.isArray(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),o.forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):t(o)?(i.endsWith("[]")&&(i=i.slice(0,-2)),Array.from(o).forEach((t,e)=>{a.append(`${i}[${e}]`,t,t.name)})):a.append(i,o,o.name),null==n?void 0:n.body){const t=e(n.body);Object.entries(t).forEach(([t,e])=>{null!=e&&a.append(t,String(e))})}return(null==n?void 0:n.onProgress)&&"undefined"!=typeof XMLHttpRequest?new Promise(async t=>{try{let e=Object.assign(Object.assign({},n),{method:c.POST,url:r,body:a});const s=[...u.interceptors.request,...this.interceptors.request];for(const t of s)e=await t(e);const o=this.getURL(e),i=this.getHeaders(e.headers||{},!0),l=new XMLHttpRequest;l.upload.addEventListener("progress",t=>{var e;if(t.lengthComputable){const s=Math.round(t.loaded/t.total*100);null===(e=n.onProgress)||void 0===e||e.call(n,{loaded:t.loaded,total:t.total,percentage:s})}}),l.addEventListener("load",async()=>{try{const e=l.getResponseHeader("Content-Type")||"";let s=null;s=e.includes("application/json")?JSON.parse(l.responseText):l.responseText;const r=[...u.interceptors.transform,...this.interceptors.transform];let o=s;for(const t of r)o=await t(o);const n=[...u.interceptors.response,...this.interceptors.response];let a=new Response(l.responseText,{status:l.status,statusText:l.statusText,headers:i});for(const t of n)a=await t(a);const c={};a.headers.forEach((t,e)=>{c[e]=t});const d=a.ok;t({data:o,success:d,error:d?null:o,status:a.status,statusText:a.statusText,headers:c})}catch(e){const s=[...u.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}),l.addEventListener("error",e=>{const s=[...u.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}),l.addEventListener("abort",()=>{t({data:null,status:0,statusText:"Aborted",headers:{},error:null,success:!1})}),l.open(e.method||c.POST,o,!0),Object.entries(i).forEach(([t,e])=>{"content-type"!==(null==t?void 0:t.toLowerCase())&&e&&l.setRequestHeader(t,e)}),l.send(e.body)}catch(e){const s=[...u.interceptors.error,...this.interceptors.error];for(const t of s)t(e);t({data:null,status:0,statusText:"Error",headers:{},error:e,success:!1})}}):this.request({method:c.POST,url:r,body:a,headers:null==n?void 0:n.headers,params:null==n?void 0:n.params,signal:null==n?void 0:n.signal,query:null==n?void 0:n.query})}static createFactory(t={}){const s=t.http||new u(t.baseURL);return function(r,o){const n=e("function"==typeof t.endpoint?t.endpoint():t.endpoint||{})[r];return{key:r,fn:async(...e)=>{const r="function"==typeof t.storage?await t.storage():t.storage;switch(r&&s.setStorage(r),o){case c.POST:return await s.post(n,...e);case c.PUT:return await s.put(n,...e);case c.PATCH:return await s.patch(n,...e);case c.DELETE:return await s.delete(n,...e);case c.HEAD:return await s.head(n,...e);case c.OPTIONS:return await s.options(n,...e);case"UPLOAD":return await s.upload(n,...e);default:return await s.get(n,...e)}}}}}}u.method=c,u.authTokenKey="access_token",u.authHeaderKey="Authorization",u.authHeaderType="Bearer",u.authDetectToken=["localStorage","sessionStorage","cookie"],u.extraHeaders=null,u.storage=null,u.interceptors={request:[],response:[],transform:[],error:[]};export{a as API_AUTH_HEADER_KEY,i as API_AUTH_HEADER_TYPE,n as API_AUTH_TOKEN_KEY,o as DEFAULT_BASE_URL,u as Http,c as HttpMethod};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
export * from "./http";
|
|
2
|
+
export * from "./logger";
|
|
3
|
+
export * from "./searchify";
|
|
4
|
+
export * from "./serialize";
|
|
5
|
+
export * from "./slugify";
|
|
1
6
|
export * from "./subscriber";
|
|
7
|
+
export * from "./syhemo";
|
|
2
8
|
export * from "./utilities";
|
|
9
|
+
export type * from "./http";
|
|
10
|
+
export type * from "./logger";
|
|
11
|
+
export type * from "./searchify";
|
|
12
|
+
export type * from "./serialize";
|
|
13
|
+
export type * from "./slugify";
|
|
3
14
|
export type * from "./subscriber";
|
|
15
|
+
export type * from "./syhemo";
|
|
4
16
|
export type * from "./utilities";
|
|
17
|
+
export type * from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./
|
|
1
|
+
"use strict";var e=require("./http.js"),t=require("./logger.js");require("./searchify.js");var r=require("./serialize.js"),s=require("./slugify.js"),i=require("./subscriber.js"),o=require("./syhemo.js"),u=require("./utilities/clone.js"),p=require("./utilities/defer.js"),l=require("./utilities/filelist.js"),a=require("./utilities/flatten.js"),x=require("./utilities/freeze.js"),c=require("./utilities/get.js"),j=require("./utilities/is-equal.js"),b=require("./utilities/is-function.js"),A=require("./utilities/object.js"),n=require("./utilities/merge.js"),q=require("./utilities/object-to-formdata.js"),E=require("./utilities/pascal-to-kebab.js"),f=require("./utilities/to-string.js"),_=require("./utilities/ucfirst.js");exports.API_AUTH_HEADER_KEY=e.API_AUTH_HEADER_KEY,exports.API_AUTH_HEADER_TYPE=e.API_AUTH_HEADER_TYPE,exports.API_AUTH_TOKEN_KEY=e.API_AUTH_TOKEN_KEY,exports.DEFAULT_BASE_URL=e.DEFAULT_BASE_URL,exports.Http=e.Http,Object.defineProperty(exports,"HttpMethod",{enumerable:!0,get:function(){return e.HttpMethod}}),exports.Logger=t.Logger,exports.Serialize=r.Serialize,exports.DEFAULT_TRANSFORMER=s.DEFAULT_TRANSFORMER,exports.slugify=s.slugify,exports.Subscriber=i.Subscriber,exports.Syhemo=o.Syhemo,exports.recordHttpRequest=o.recordHttpRequest,exports.clone=u.clone,exports.defer=p.defer,exports.deferAsync=p.deferAsync,exports.isFileList=l.isFileList,exports.escapeRegexKey=a.escapeRegexKey,exports.flatten=a.flatten,exports.flattenToArray=a.flattenToArray,exports.freeze=x.freeze,exports.get=c.get,exports.isEqual=j.isEqual,exports.isFunction=b.isFunction,exports.hasOwnProperty=A.hasOwnProperty,exports.isComplexObject=A.isComplexObject,exports.isLiteralObject=A.isLiteralObject,exports.isObject=A.isObject,exports.isObjectable=A.isObjectable,exports.merge=n.merge,exports.objectToFormData=q.objectToFormData,exports.pascalToKebab=E.pascalToKebab,exports.toString=f.toString,exports.ucfirst=_.ucfirst;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{Subscriber}from"./subscriber.mjs";export{clone}from"./utilities/clone.mjs";export{freeze}from"./utilities/freeze.mjs";export{isEqual}from"./utilities/is-equal.mjs";export{isFunction}from"./utilities/is-function.mjs";export{hasOwnProperty,isComplexObject,isLiteralObject,isObject,isObjectable}from"./utilities/object.mjs";export{merge}from"./utilities/merge.mjs";export{toString}from"./utilities/to-string.mjs";export{ucfirst}from"./utilities/ucfirst.mjs";
|
|
1
|
+
export{API_AUTH_HEADER_KEY,API_AUTH_HEADER_TYPE,API_AUTH_TOKEN_KEY,DEFAULT_BASE_URL,Http,HttpMethod}from"./http.mjs";export{Logger}from"./logger.mjs";import"./searchify.mjs";export{Serialize}from"./serialize.mjs";export{DEFAULT_TRANSFORMER,slugify}from"./slugify.mjs";export{Subscriber}from"./subscriber.mjs";export{Syhemo,recordHttpRequest}from"./syhemo.mjs";export{clone}from"./utilities/clone.mjs";export{defer,deferAsync}from"./utilities/defer.mjs";export{isFileList}from"./utilities/filelist.mjs";export{escapeRegexKey,flatten,flattenToArray}from"./utilities/flatten.mjs";export{freeze}from"./utilities/freeze.mjs";export{get}from"./utilities/get.mjs";export{isEqual}from"./utilities/is-equal.mjs";export{isFunction}from"./utilities/is-function.mjs";export{hasOwnProperty,isComplexObject,isLiteralObject,isObject,isObjectable}from"./utilities/object.mjs";export{merge}from"./utilities/merge.mjs";export{objectToFormData}from"./utilities/object-to-formdata.mjs";export{pascalToKebab}from"./utilities/pascal-to-kebab.mjs";export{toString}from"./utilities/to-string.mjs";export{ucfirst}from"./utilities/ucfirst.mjs";
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Log severity level. */
|
|
2
|
+
export type LogLevel = "LOG" | "WARN" | "ERROR" | "DEBUG" | "VERBOSE";
|
|
3
|
+
/** A buffered log entry stored in the shared global log buffer. */
|
|
4
|
+
export interface LogEntry {
|
|
5
|
+
/** Unix timestamp in milliseconds. */
|
|
6
|
+
timestamp: number;
|
|
7
|
+
/** Severity level of the log entry. */
|
|
8
|
+
level: LogLevel;
|
|
9
|
+
/** Logger context name (e.g. module or class name). */
|
|
10
|
+
context: string;
|
|
11
|
+
/** The logged message. */
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Isomorphic logger with color-coded output.
|
|
16
|
+
*
|
|
17
|
+
* - **Server (Node.js)**: Uses ANSI escape codes for terminal coloring.
|
|
18
|
+
* - **Browser**: Uses `%c` CSS formatting for DevTools console.
|
|
19
|
+
*
|
|
20
|
+
* All log entries are buffered in a shared `globalThis` store so that
|
|
21
|
+
* {@link Syhemo} can collect them for monitoring.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const logger = new Logger("MyModule");
|
|
26
|
+
* logger.log("Server started on port 3000");
|
|
27
|
+
* logger.warn("Deprecated API used");
|
|
28
|
+
* logger.error("Connection failed", error);
|
|
29
|
+
* logger.debug("Payload:", data);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class Logger {
|
|
33
|
+
private readonly context;
|
|
34
|
+
/**
|
|
35
|
+
* @param context - Label shown in log output, e.g. `[MyModule]`.
|
|
36
|
+
* Defaults to `"@ecosy"`.
|
|
37
|
+
*/
|
|
38
|
+
constructor(context?: string);
|
|
39
|
+
private format;
|
|
40
|
+
private formatServer;
|
|
41
|
+
private formatBrowser;
|
|
42
|
+
/** Log a message at the `LOG` level (green). */
|
|
43
|
+
log(message: string, ...args: unknown[]): void;
|
|
44
|
+
/** Log a message at the `WARN` level (yellow). */
|
|
45
|
+
warn(message: string, ...args: unknown[]): void;
|
|
46
|
+
/** Log a message at the `ERROR` level (red). */
|
|
47
|
+
error(message: string, ...args: unknown[]): void;
|
|
48
|
+
/** Log a message at the `DEBUG` level (magenta). */
|
|
49
|
+
debug(message: string, ...args: unknown[]): void;
|
|
50
|
+
/** Log a message at the `VERBOSE` level (cyan). */
|
|
51
|
+
verbose(message: string, ...args: unknown[]): void;
|
|
52
|
+
/**
|
|
53
|
+
* Returns a shallow copy of all buffered log entries (non-destructive).
|
|
54
|
+
* Used by {@link Syhemo} to collect logs for monitoring snapshots.
|
|
55
|
+
*/
|
|
56
|
+
static getLogs(): LogEntry[];
|
|
57
|
+
/**
|
|
58
|
+
* Returns the current error, warn, and total log counts, then resets
|
|
59
|
+
* the counters to zero. Used by {@link Syhemo} for per-interval log rate tracking.
|
|
60
|
+
*/
|
|
61
|
+
static drainCounts(): {
|
|
62
|
+
errors: number;
|
|
63
|
+
warns: number;
|
|
64
|
+
total: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const o=()=>"undefined"==typeof window,t="[0m",r="[1m",e="[33m",n="[37m",s={LOG:"color:#22c55e;font-weight:bold",WARN:"color:#eab308;font-weight:bold",ERROR:"color:#ef4444;font-weight:bold",DEBUG:"color:#a855f7;font-weight:bold",VERBOSE:"color:#06b6d4;font-weight:bold"},c="color:inherit;font-weight:normal",a={LOG:"color:#22c55e",WARN:"color:#eab308",ERROR:"color:#ef4444",DEBUG:"color:#a855f7",VERBOSE:"color:#06b6d4"};function i(){return(new Date).toLocaleString("en-US",{month:"2-digit",day:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0})}const f={LOG:"[32m",WARN:e,ERROR:"[31m",DEBUG:"[35m",VERBOSE:"[36m"},l=Symbol.for("@ecosy:logger"),m=globalThis;m[l]||(m[l]={buffer:[],errors:0,warns:0,total:0});const R=m[l];exports.Logger=class{constructor(o="@ecosy"){this.context=o}format(t,r,...e){const n="ERROR"===t?console.error:"WARN"===t?console.warn:console.log;R.buffer.push({timestamp:Date.now(),level:t,context:this.context,message:r}),R.buffer.length>200&&R.buffer.shift(),R.total++,"ERROR"===t&&R.errors++,"WARN"===t&&R.warns++,o()?this.formatServer(n,t,r,...e):this.formatBrowser(n,t,r,...e)}formatServer(s,c,a,...l){const m=f[c],R=`${n}${i()}${t}`;s(`${`${m}${r}${c.padEnd(7)}${t}`} ${`${e}${o()&&"undefined"!=typeof process?process.pid:0}${t}`} - ${R} ${`${e}[${this.context}]${t}`} ${`${m}${a}${t}`}`,...l)}formatBrowser(o,t,r,...e){o(`%c${t.padEnd(7)}%c - %c${i()}%c %c[${this.context}]%c %c${r}`,s[t],c,"color:#9ca3af;font-weight:normal",c,"color:#eab308;font-weight:normal",c,a[t],...e)}log(o,...t){this.format("LOG",o,...t)}warn(o,...t){this.format("WARN",o,...t)}error(o,...t){this.format("ERROR",o,...t)}debug(o,...t){this.format("DEBUG",o,...t)}verbose(o,...t){this.format("VERBOSE",o,...t)}static getLogs(){return[...R.buffer]}static drainCounts(){const o={errors:R.errors,warns:R.warns,total:R.total};return R.errors=0,R.warns=0,R.total=0,o}};
|
package/dist/logger.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const o=()=>"undefined"==typeof window,t="[0m",r="[1m",e="[33m",n="[37m",s={LOG:"color:#22c55e;font-weight:bold",WARN:"color:#eab308;font-weight:bold",ERROR:"color:#ef4444;font-weight:bold",DEBUG:"color:#a855f7;font-weight:bold",VERBOSE:"color:#06b6d4;font-weight:bold"},c="color:inherit;font-weight:normal",a={LOG:"color:#22c55e",WARN:"color:#eab308",ERROR:"color:#ef4444",DEBUG:"color:#a855f7",VERBOSE:"color:#06b6d4"};function i(){return(new Date).toLocaleString("en-US",{month:"2-digit",day:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0})}const f={LOG:"[32m",WARN:e,ERROR:"[31m",DEBUG:"[35m",VERBOSE:"[36m"},l=Symbol.for("@ecosy:logger"),m=globalThis;m[l]||(m[l]={buffer:[],errors:0,warns:0,total:0});const R=m[l];class h{constructor(o="@ecosy"){this.context=o}format(t,r,...e){const n="ERROR"===t?console.error:"WARN"===t?console.warn:console.log;R.buffer.push({timestamp:Date.now(),level:t,context:this.context,message:r}),R.buffer.length>200&&R.buffer.shift(),R.total++,"ERROR"===t&&R.errors++,"WARN"===t&&R.warns++,o()?this.formatServer(n,t,r,...e):this.formatBrowser(n,t,r,...e)}formatServer(s,c,a,...l){const m=f[c],R=`${n}${i()}${t}`;s(`${`${m}${r}${c.padEnd(7)}${t}`} ${`${e}${o()&&"undefined"!=typeof process?process.pid:0}${t}`} - ${R} ${`${e}[${this.context}]${t}`} ${`${m}${a}${t}`}`,...l)}formatBrowser(o,t,r,...e){o(`%c${t.padEnd(7)}%c - %c${i()}%c %c[${this.context}]%c %c${r}`,s[t],c,"color:#9ca3af;font-weight:normal",c,"color:#eab308;font-weight:normal",c,a[t],...e)}log(o,...t){this.format("LOG",o,...t)}warn(o,...t){this.format("WARN",o,...t)}error(o,...t){this.format("ERROR",o,...t)}debug(o,...t){this.format("DEBUG",o,...t)}verbose(o,...t){this.format("VERBOSE",o,...t)}static getLogs(){return[...R.buffer]}static drainCounts(){const o={errors:R.errors,warns:R.warns,total:R.total};return R.errors=0,R.warns=0,R.total=0,o}}export{h as Logger};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Position of a match within the original string. */
|
|
2
|
+
export interface SearchPosition {
|
|
3
|
+
/** Start index in the original string. */
|
|
4
|
+
start: number;
|
|
5
|
+
/** Length of the matched substring. */
|
|
6
|
+
length: number;
|
|
7
|
+
}
|
|
8
|
+
/** Result of a {@link searchify} call. */
|
|
9
|
+
export interface SearchResult {
|
|
10
|
+
/** Array of matched substrings from the original string. */
|
|
11
|
+
matches: string[];
|
|
12
|
+
/** Corresponding positions for each match. */
|
|
13
|
+
positions: SearchPosition[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Performs a diacritic-insensitive fuzzy search by comparing slugified characters.
|
|
17
|
+
* Returns all matching substrings and their positions in the original string.
|
|
18
|
+
*
|
|
19
|
+
* Uses a **sliding window** algorithm with per-character slugification cache
|
|
20
|
+
* for optimal performance on repeated characters.
|
|
21
|
+
*
|
|
22
|
+
* @param original - The original string to search within.
|
|
23
|
+
* @param searchStr - The search term (will be slugified for comparison).
|
|
24
|
+
* @returns A {@link SearchResult} with matched substrings and their positions.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* searchify("Xin Chào Việt Nam", "viet");
|
|
29
|
+
* // { matches: ["Việt"], positions: [{ start: 9, length: 4 }] }
|
|
30
|
+
*
|
|
31
|
+
* searchify("Crème Brûlée", "brulee");
|
|
32
|
+
* // { matches: ["Brûlée"], positions: [{ start: 6, length: 6 }] }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export default function searchify(original: string, searchStr: string): SearchResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./slugify.js");const t={separator:"",silent:!0,transformer:Object.assign({},e.DEFAULT_TRANSFORMER)};exports.default=function(i,s){const n=e.slugify(s,t),r=n.length,o={matches:[],positions:[]};if(!r)return o;const l=new Map,g=[...i.split("")].map((i,s)=>{let n=l.get(i);return void 0===n&&(n=e.slugify(i,t),l.set(i,n)),{originIndex:s,slugified:n,char:i}}).filter((e,t,i)=>0===t||(" "!==e.char||" "!==i[t-1].char));if(g.length<r)return o;for(let e=0;e<g.length;){const t=g[e];if(""===t.slugified){e++;continue}let s="",l=e,u=!0;for(;s.length<r&&l<g.length;){const e=g[l];if(""!==e.slugified&&(s+=e.slugified,!n.startsWith(s))){u=!1;break}l++}if(u&&s===n){const s=g[l-1],n=i.substring(t.originIndex,s.originIndex+1);o.matches.push(n),o.positions.push({start:t.originIndex,length:n.length}),e=l}else e++}return o};
|