@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.
Files changed (47) hide show
  1. package/README.md +40 -198
  2. package/dist/http.d.ts +176 -0
  3. package/dist/http.js +1 -0
  4. package/dist/http.mjs +1 -0
  5. package/dist/index.d.ts +13 -0
  6. package/dist/index.js +1 -1
  7. package/dist/index.mjs +1 -1
  8. package/dist/logger.d.ts +66 -0
  9. package/dist/logger.js +1 -0
  10. package/dist/logger.mjs +1 -0
  11. package/dist/searchify.d.ts +35 -0
  12. package/dist/searchify.js +1 -0
  13. package/dist/searchify.mjs +1 -0
  14. package/dist/serialize.d.ts +80 -0
  15. package/dist/serialize.js +1 -0
  16. package/dist/serialize.mjs +1 -0
  17. package/dist/slugify.d.ts +34 -0
  18. package/dist/slugify.js +1 -0
  19. package/dist/slugify.mjs +1 -0
  20. package/dist/syhemo.d.ts +102 -0
  21. package/dist/syhemo.js +1 -0
  22. package/dist/syhemo.mjs +1 -0
  23. package/dist/types/built-in.d.ts +22 -1
  24. package/dist/utilities/defer.d.ts +53 -0
  25. package/dist/utilities/defer.js +1 -0
  26. package/dist/utilities/defer.mjs +1 -0
  27. package/dist/utilities/filelist.d.ts +25 -0
  28. package/dist/utilities/filelist.js +1 -0
  29. package/dist/utilities/filelist.mjs +1 -0
  30. package/dist/utilities/flatten.d.ts +57 -0
  31. package/dist/utilities/flatten.js +1 -0
  32. package/dist/utilities/flatten.mjs +1 -0
  33. package/dist/utilities/get.d.ts +21 -0
  34. package/dist/utilities/get.js +1 -0
  35. package/dist/utilities/get.mjs +1 -0
  36. package/dist/utilities/index.d.ts +6 -0
  37. package/dist/utilities/index.js +1 -1
  38. package/dist/utilities/index.mjs +1 -1
  39. package/dist/utilities/object-to-formdata.d.ts +26 -0
  40. package/dist/utilities/object-to-formdata.js +1 -0
  41. package/dist/utilities/object-to-formdata.mjs +1 -0
  42. package/dist/utilities/object.js +1 -1
  43. package/dist/utilities/object.mjs +1 -1
  44. package/dist/utilities/pascal-to-kebab.d.ts +15 -0
  45. package/dist/utilities/pascal-to-kebab.js +1 -0
  46. package/dist/utilities/pascal-to-kebab.mjs +1 -0
  47. package/package.json +39 -3
package/README.md CHANGED
@@ -1,234 +1,76 @@
1
1
  # @ecosy/core
2
2
 
3
- Lightweight utilities, pub/sub subscriber, and types for TypeScript applications.
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
- ## Modules
11
+ ## Features
12
+
13
+ ### Subpath Imports
14
14
 
15
15
  | Entry point | Description |
16
16
  |--|--|
17
- | `@ecosy/core` | Re-exports utilities + subscriber |
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 structural equality check. Supports primitives, arrays, plain objects, Date, RegExp, Map, Set, ArrayBuffer, and TypedArrays.
30
+ Deep utility types for TypeScript `Freezable<T>`, `PartialLiteral<T>`, `Promisable<V>`, `ToString<T>`, and more.
96
31
 
97
- ```ts
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
- ### `isObject(value): value is object`
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
- Checks if a value is a non-null object.
43
+ ### Subscriber
118
44
 
119
- ### `isLiteralObject(value): value is LiteralObject`
45
+ Pub/sub event emitter with built-in state management, async once, and typed wiring.
120
46
 
121
- Checks if a value is a plain object (`{}` or `Object.create(null)`).
47
+ ### Http
122
48
 
123
- ### `isComplexObject<T>(value): value is T`
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
- Checks if a value is a non-array object (e.g., class instance).
51
+ ### Serialize
126
52
 
127
- ### `isObjectable(value): value is Objectable`
128
-
129
- Checks if a value is an object, array, or function.
130
-
131
- ### `hasOwnProperty(obj, key): boolean`
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
- ### `ucfirst<T>(str: T): Capitalize<T>`
59
+ ### Slugify & Searchify
145
60
 
146
- Capitalizes the first character of a string.
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 packages
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("./subscriber.js"),i=require("./utilities/clone.js"),r=require("./utilities/freeze.js"),s=require("./utilities/is-equal.js"),t=require("./utilities/is-function.js"),u=require("./utilities/object.js"),o=require("./utilities/merge.js"),c=require("./utilities/to-string.js"),l=require("./utilities/ucfirst.js");exports.Subscriber=e.Subscriber,exports.clone=i.clone,exports.freeze=r.freeze,exports.isEqual=s.isEqual,exports.isFunction=t.isFunction,exports.hasOwnProperty=u.hasOwnProperty,exports.isComplexObject=u.isComplexObject,exports.isLiteralObject=u.isLiteralObject,exports.isObject=u.isObject,exports.isObjectable=u.isObjectable,exports.merge=o.merge,exports.toString=c.toString,exports.ucfirst=l.ucfirst;
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";
@@ -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="",r="",e="",n="",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:"",WARN:e,ERROR:"",DEBUG:"",VERBOSE:""},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}};
@@ -0,0 +1 @@
1
+ const o=()=>"undefined"==typeof window,t="",r="",e="",n="",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:"",WARN:e,ERROR:"",DEBUG:"",VERBOSE:""},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};