astatus 0.1.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 +178 -0
- package/dist/index.cjs +199 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +175 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# astatus
|
|
2
|
+
|
|
3
|
+
Async status signal. No data, no fetch — just the stage.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import astatus from "astatus";
|
|
7
|
+
|
|
8
|
+
const AS = astatus();
|
|
9
|
+
|
|
10
|
+
AS.pending();
|
|
11
|
+
await fetchUser();
|
|
12
|
+
await processData(); // however many steps
|
|
13
|
+
AS.success(); // you decide when
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Most async state libraries tie status to data or fetch logic. `astatus` doesn't.
|
|
17
|
+
It's a standalone signal you inject into any flow, at any point you choose.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install astatus
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import astatus, { STATUS } from "astatus";
|
|
33
|
+
|
|
34
|
+
const loginAS = astatus({ name: "login" });
|
|
35
|
+
|
|
36
|
+
// Inject status at the right moment
|
|
37
|
+
loginAS.pending();
|
|
38
|
+
const data = await fetchUser();
|
|
39
|
+
await syncStore(data); // sync or async, as many steps as needed
|
|
40
|
+
loginAS.success();
|
|
41
|
+
|
|
42
|
+
// Read
|
|
43
|
+
console.log(loginAS.status); // 'success'
|
|
44
|
+
console.log(loginAS.isSuccess); // true
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
> All examples use `const AS = astatus()` unless otherwise noted.
|
|
52
|
+
|
|
53
|
+
### Create
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
astatus({ name?, status? })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Option | Type | Default | Description |
|
|
60
|
+
| -------- | -------- | ----------- | ------------------- |
|
|
61
|
+
| `name` | `string` | `null` | Name for debug logs |
|
|
62
|
+
| `status` | `string` | `'initial'` | Initial status |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### Inject
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
AS.initial()
|
|
70
|
+
AS.pending()
|
|
71
|
+
AS.success()
|
|
72
|
+
AS.failure(error?) // optional error value
|
|
73
|
+
AS.custom('uploading') // any string outside built-in stages
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Read
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
AS.status; // 'initial' | 'pending' | 'success' | 'failure' | custom
|
|
82
|
+
AS.error; // value passed to failure(), otherwise null
|
|
83
|
+
AS.isInitial;
|
|
84
|
+
AS.isPending;
|
|
85
|
+
AS.isSuccess;
|
|
86
|
+
AS.isFailure;
|
|
87
|
+
AS.isCustom; // true if current status is not a built-in stage
|
|
88
|
+
AS.isLocked;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### Observe
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// All changes
|
|
97
|
+
const unsub = AS.subscribe((curr, prev) => {
|
|
98
|
+
console.log(curr.status, prev.status)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Specific status — triggers on both entry and exit
|
|
102
|
+
const unwatch = AS.watch('success', (curr, prev) => { ... })
|
|
103
|
+
AS.watch(['success', 'failure'], (curr, prev) => { ... })
|
|
104
|
+
|
|
105
|
+
// Entry only — triggers once on transition into the status
|
|
106
|
+
const unwatch = AS.when('success', (curr, prev) => { ... })
|
|
107
|
+
|
|
108
|
+
// Cleanup
|
|
109
|
+
unsub()
|
|
110
|
+
unwatch()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### Wait
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
// As a timing gate — wait for the right moment, then continue
|
|
119
|
+
await AS.wait();
|
|
120
|
+
await AS.wait("success");
|
|
121
|
+
|
|
122
|
+
// As a result — inspect what happened
|
|
123
|
+
const { status, error, timeout, immediate } = await AS.wait();
|
|
124
|
+
const result = await AS.wait(["success", "failure"], 15); // timeout in seconds
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Field | Description |
|
|
128
|
+
| ----------- | ---------------------------------- |
|
|
129
|
+
| `status` | Status at resolve time (snapshot) |
|
|
130
|
+
| `error` | Error at resolve time |
|
|
131
|
+
| `timeout` | `true` if timed out |
|
|
132
|
+
| `immediate` | `true` if already in target status |
|
|
133
|
+
| `destroyed` | `true` if instance was destroyed |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### Lock
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
AS.lock(); // block all status changes
|
|
141
|
+
AS.unlock(); // unblock
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### Reset / Destroy
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
AS.reset(); // back to initial, clears lock
|
|
150
|
+
AS.destroy(); // clears all listeners, blocks all further changes
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### STATUS constant
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
import { STATUS } from "astatus";
|
|
159
|
+
|
|
160
|
+
STATUS.INITIAL; // 'initial'
|
|
161
|
+
STATUS.PENDING; // 'pending'
|
|
162
|
+
STATUS.SUCCESS; // 'success'
|
|
163
|
+
STATUS.FAILURE; // 'failure'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Notes
|
|
169
|
+
|
|
170
|
+
- `subscribe` / `watch` / `when` return an unsubscribe function — call it to clean up.
|
|
171
|
+
- `destroy()` clears all listeners at once. Useful on route change or component unmount.
|
|
172
|
+
- Listener errors are caught and logged individually without breaking other listeners.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
STATUS: () => STATUS,
|
|
23
|
+
default: () => index_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var STATUS = Object.freeze({
|
|
27
|
+
INITIAL: "initial",
|
|
28
|
+
PENDING: "pending",
|
|
29
|
+
SUCCESS: "success",
|
|
30
|
+
FAILURE: "failure"
|
|
31
|
+
});
|
|
32
|
+
var astatus = (options = {}) => {
|
|
33
|
+
const { name = null } = options;
|
|
34
|
+
const _prefix = `[astatus${name ? `:${name}` : ""}]`;
|
|
35
|
+
const _builtinStatuses = new Set(Object.values(STATUS));
|
|
36
|
+
let _status = options.status ?? STATUS.INITIAL;
|
|
37
|
+
let _error = null;
|
|
38
|
+
let _locked = false;
|
|
39
|
+
let _destroyed = false;
|
|
40
|
+
const _listeners = /* @__PURE__ */ new Set();
|
|
41
|
+
const _notify = (prevStatus, prevError) => {
|
|
42
|
+
const curr = { status: _status, error: _error };
|
|
43
|
+
const prev = { status: prevStatus, error: prevError };
|
|
44
|
+
_listeners.forEach((fn) => {
|
|
45
|
+
try {
|
|
46
|
+
fn(curr, prev);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error(`${_prefix} subscriber error:`, e);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const _set = (newStatus, newError = null) => {
|
|
53
|
+
if (_locked || _destroyed) return;
|
|
54
|
+
if (_status === newStatus && _error === newError) return;
|
|
55
|
+
const prevStatus = _status;
|
|
56
|
+
const prevError = _error;
|
|
57
|
+
_status = newStatus;
|
|
58
|
+
_error = newError;
|
|
59
|
+
_notify(prevStatus, prevError);
|
|
60
|
+
};
|
|
61
|
+
const isInitial = () => _status === STATUS.INITIAL;
|
|
62
|
+
const isPending = () => _status === STATUS.PENDING;
|
|
63
|
+
const isSuccess = () => _status === STATUS.SUCCESS;
|
|
64
|
+
const isFailure = () => _status === STATUS.FAILURE;
|
|
65
|
+
const isCustom = () => !_builtinStatuses.has(_status);
|
|
66
|
+
const initial = () => _set(STATUS.INITIAL);
|
|
67
|
+
const pending = () => _set(STATUS.PENDING);
|
|
68
|
+
const success = () => _set(STATUS.SUCCESS);
|
|
69
|
+
const failure = (error = null) => _set(STATUS.FAILURE, error);
|
|
70
|
+
const custom = (type) => {
|
|
71
|
+
if (!type) {
|
|
72
|
+
console.error(`${_prefix} custom() requires a non-empty string`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
_set(type);
|
|
76
|
+
};
|
|
77
|
+
const lock = () => {
|
|
78
|
+
_locked = true;
|
|
79
|
+
};
|
|
80
|
+
const unlock = () => {
|
|
81
|
+
_locked = false;
|
|
82
|
+
};
|
|
83
|
+
const subscribe = (fn) => {
|
|
84
|
+
if (_destroyed) return () => {
|
|
85
|
+
};
|
|
86
|
+
_listeners.add(fn);
|
|
87
|
+
return () => _listeners.delete(fn);
|
|
88
|
+
};
|
|
89
|
+
const watch = (types, fn) => {
|
|
90
|
+
const _types = Array.isArray(types) ? types : [types];
|
|
91
|
+
return subscribe((curr, prev) => {
|
|
92
|
+
if (_types.includes(curr.status) || _types.includes(prev.status))
|
|
93
|
+
fn(curr, prev);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
const when = (type, fn) => {
|
|
97
|
+
return subscribe((curr, prev) => {
|
|
98
|
+
if (curr.status === type && prev.status !== type) fn(curr, prev);
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const wait = (types = [STATUS.SUCCESS, STATUS.FAILURE], timeoutSec = 10) => {
|
|
102
|
+
const _types = typeof types === "string" ? [types] : types;
|
|
103
|
+
if (_destroyed) {
|
|
104
|
+
return Promise.resolve({
|
|
105
|
+
timeout: false,
|
|
106
|
+
immediate: true,
|
|
107
|
+
destroyed: true,
|
|
108
|
+
status: _status,
|
|
109
|
+
error: _error
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
if (_types.includes(_status)) {
|
|
114
|
+
resolve({
|
|
115
|
+
timeout: false,
|
|
116
|
+
immediate: true,
|
|
117
|
+
status: _status,
|
|
118
|
+
error: _error
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let unsubscribe;
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
unsubscribe();
|
|
125
|
+
resolve({
|
|
126
|
+
timeout: true,
|
|
127
|
+
immediate: false,
|
|
128
|
+
status: _status,
|
|
129
|
+
error: _error
|
|
130
|
+
});
|
|
131
|
+
}, timeoutSec * 1e3);
|
|
132
|
+
unsubscribe = subscribe(({ status, error }) => {
|
|
133
|
+
if (_types.includes(status)) {
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
unsubscribe();
|
|
136
|
+
resolve({
|
|
137
|
+
timeout: false,
|
|
138
|
+
immediate: false,
|
|
139
|
+
status,
|
|
140
|
+
error
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
const reset = () => {
|
|
147
|
+
if (_destroyed) return;
|
|
148
|
+
_locked = false;
|
|
149
|
+
_set(STATUS.INITIAL);
|
|
150
|
+
};
|
|
151
|
+
const destroy = () => {
|
|
152
|
+
_listeners.clear();
|
|
153
|
+
_destroyed = true;
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
get status() {
|
|
157
|
+
return _status;
|
|
158
|
+
},
|
|
159
|
+
get error() {
|
|
160
|
+
return _error;
|
|
161
|
+
},
|
|
162
|
+
get isInitial() {
|
|
163
|
+
return isInitial();
|
|
164
|
+
},
|
|
165
|
+
get isPending() {
|
|
166
|
+
return isPending();
|
|
167
|
+
},
|
|
168
|
+
get isSuccess() {
|
|
169
|
+
return isSuccess();
|
|
170
|
+
},
|
|
171
|
+
get isFailure() {
|
|
172
|
+
return isFailure();
|
|
173
|
+
},
|
|
174
|
+
get isCustom() {
|
|
175
|
+
return isCustom();
|
|
176
|
+
},
|
|
177
|
+
get isLocked() {
|
|
178
|
+
return _locked;
|
|
179
|
+
},
|
|
180
|
+
initial,
|
|
181
|
+
pending,
|
|
182
|
+
success,
|
|
183
|
+
failure,
|
|
184
|
+
custom,
|
|
185
|
+
lock,
|
|
186
|
+
unlock,
|
|
187
|
+
subscribe,
|
|
188
|
+
watch,
|
|
189
|
+
when,
|
|
190
|
+
wait,
|
|
191
|
+
reset,
|
|
192
|
+
destroy
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
var index_default = astatus;
|
|
196
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
197
|
+
0 && (module.exports = {
|
|
198
|
+
STATUS
|
|
199
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare const STATUS: Readonly<{
|
|
2
|
+
readonly INITIAL: "initial";
|
|
3
|
+
readonly PENDING: "pending";
|
|
4
|
+
readonly SUCCESS: "success";
|
|
5
|
+
readonly FAILURE: "failure";
|
|
6
|
+
}>;
|
|
7
|
+
type StatusValue = (typeof STATUS)[keyof typeof STATUS];
|
|
8
|
+
interface AstatusState {
|
|
9
|
+
status: string;
|
|
10
|
+
error: unknown;
|
|
11
|
+
}
|
|
12
|
+
type AstatusListener = (curr: AstatusState, prev: AstatusState) => void;
|
|
13
|
+
interface AstatusOptions {
|
|
14
|
+
status?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}
|
|
17
|
+
interface AstatusWaitResult {
|
|
18
|
+
timeout: boolean;
|
|
19
|
+
immediate: boolean;
|
|
20
|
+
destroyed?: boolean;
|
|
21
|
+
status: string;
|
|
22
|
+
error: unknown;
|
|
23
|
+
}
|
|
24
|
+
interface Astatus {
|
|
25
|
+
readonly status: string;
|
|
26
|
+
readonly error: unknown;
|
|
27
|
+
readonly isInitial: boolean;
|
|
28
|
+
readonly isPending: boolean;
|
|
29
|
+
readonly isSuccess: boolean;
|
|
30
|
+
readonly isFailure: boolean;
|
|
31
|
+
readonly isCustom: boolean;
|
|
32
|
+
readonly isLocked: boolean;
|
|
33
|
+
initial: () => void;
|
|
34
|
+
pending: () => void;
|
|
35
|
+
success: () => void;
|
|
36
|
+
failure: (error?: unknown) => void;
|
|
37
|
+
custom: (type: string) => void;
|
|
38
|
+
lock: () => void;
|
|
39
|
+
unlock: () => void;
|
|
40
|
+
subscribe: (fn: AstatusListener) => () => void;
|
|
41
|
+
watch: (types: string | string[], fn: AstatusListener) => () => void;
|
|
42
|
+
when: (type: string, fn: AstatusListener) => () => void;
|
|
43
|
+
wait: (types?: string | string[], timeoutSec?: number) => Promise<AstatusWaitResult>;
|
|
44
|
+
reset: () => void;
|
|
45
|
+
destroy: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* astatus
|
|
49
|
+
*
|
|
50
|
+
* A signal that manages only the stage of an async operation
|
|
51
|
+
* (initial / pending / success / failure).
|
|
52
|
+
* Completely decoupled from data and fetch logic —
|
|
53
|
+
* you decide when and where to inject each status.
|
|
54
|
+
*
|
|
55
|
+
* @param options.status - Initial status (default: 'initial')
|
|
56
|
+
* @param options.name - Name for debug logs
|
|
57
|
+
*/
|
|
58
|
+
declare const astatus: (options?: AstatusOptions) => Astatus;
|
|
59
|
+
|
|
60
|
+
export { type Astatus, type AstatusListener, type AstatusOptions, type AstatusState, type AstatusWaitResult, STATUS, type StatusValue, astatus as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare const STATUS: Readonly<{
|
|
2
|
+
readonly INITIAL: "initial";
|
|
3
|
+
readonly PENDING: "pending";
|
|
4
|
+
readonly SUCCESS: "success";
|
|
5
|
+
readonly FAILURE: "failure";
|
|
6
|
+
}>;
|
|
7
|
+
type StatusValue = (typeof STATUS)[keyof typeof STATUS];
|
|
8
|
+
interface AstatusState {
|
|
9
|
+
status: string;
|
|
10
|
+
error: unknown;
|
|
11
|
+
}
|
|
12
|
+
type AstatusListener = (curr: AstatusState, prev: AstatusState) => void;
|
|
13
|
+
interface AstatusOptions {
|
|
14
|
+
status?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}
|
|
17
|
+
interface AstatusWaitResult {
|
|
18
|
+
timeout: boolean;
|
|
19
|
+
immediate: boolean;
|
|
20
|
+
destroyed?: boolean;
|
|
21
|
+
status: string;
|
|
22
|
+
error: unknown;
|
|
23
|
+
}
|
|
24
|
+
interface Astatus {
|
|
25
|
+
readonly status: string;
|
|
26
|
+
readonly error: unknown;
|
|
27
|
+
readonly isInitial: boolean;
|
|
28
|
+
readonly isPending: boolean;
|
|
29
|
+
readonly isSuccess: boolean;
|
|
30
|
+
readonly isFailure: boolean;
|
|
31
|
+
readonly isCustom: boolean;
|
|
32
|
+
readonly isLocked: boolean;
|
|
33
|
+
initial: () => void;
|
|
34
|
+
pending: () => void;
|
|
35
|
+
success: () => void;
|
|
36
|
+
failure: (error?: unknown) => void;
|
|
37
|
+
custom: (type: string) => void;
|
|
38
|
+
lock: () => void;
|
|
39
|
+
unlock: () => void;
|
|
40
|
+
subscribe: (fn: AstatusListener) => () => void;
|
|
41
|
+
watch: (types: string | string[], fn: AstatusListener) => () => void;
|
|
42
|
+
when: (type: string, fn: AstatusListener) => () => void;
|
|
43
|
+
wait: (types?: string | string[], timeoutSec?: number) => Promise<AstatusWaitResult>;
|
|
44
|
+
reset: () => void;
|
|
45
|
+
destroy: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* astatus
|
|
49
|
+
*
|
|
50
|
+
* A signal that manages only the stage of an async operation
|
|
51
|
+
* (initial / pending / success / failure).
|
|
52
|
+
* Completely decoupled from data and fetch logic —
|
|
53
|
+
* you decide when and where to inject each status.
|
|
54
|
+
*
|
|
55
|
+
* @param options.status - Initial status (default: 'initial')
|
|
56
|
+
* @param options.name - Name for debug logs
|
|
57
|
+
*/
|
|
58
|
+
declare const astatus: (options?: AstatusOptions) => Astatus;
|
|
59
|
+
|
|
60
|
+
export { type Astatus, type AstatusListener, type AstatusOptions, type AstatusState, type AstatusWaitResult, STATUS, type StatusValue, astatus as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var STATUS = Object.freeze({
|
|
3
|
+
INITIAL: "initial",
|
|
4
|
+
PENDING: "pending",
|
|
5
|
+
SUCCESS: "success",
|
|
6
|
+
FAILURE: "failure"
|
|
7
|
+
});
|
|
8
|
+
var astatus = (options = {}) => {
|
|
9
|
+
const { name = null } = options;
|
|
10
|
+
const _prefix = `[astatus${name ? `:${name}` : ""}]`;
|
|
11
|
+
const _builtinStatuses = new Set(Object.values(STATUS));
|
|
12
|
+
let _status = options.status ?? STATUS.INITIAL;
|
|
13
|
+
let _error = null;
|
|
14
|
+
let _locked = false;
|
|
15
|
+
let _destroyed = false;
|
|
16
|
+
const _listeners = /* @__PURE__ */ new Set();
|
|
17
|
+
const _notify = (prevStatus, prevError) => {
|
|
18
|
+
const curr = { status: _status, error: _error };
|
|
19
|
+
const prev = { status: prevStatus, error: prevError };
|
|
20
|
+
_listeners.forEach((fn) => {
|
|
21
|
+
try {
|
|
22
|
+
fn(curr, prev);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(`${_prefix} subscriber error:`, e);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const _set = (newStatus, newError = null) => {
|
|
29
|
+
if (_locked || _destroyed) return;
|
|
30
|
+
if (_status === newStatus && _error === newError) return;
|
|
31
|
+
const prevStatus = _status;
|
|
32
|
+
const prevError = _error;
|
|
33
|
+
_status = newStatus;
|
|
34
|
+
_error = newError;
|
|
35
|
+
_notify(prevStatus, prevError);
|
|
36
|
+
};
|
|
37
|
+
const isInitial = () => _status === STATUS.INITIAL;
|
|
38
|
+
const isPending = () => _status === STATUS.PENDING;
|
|
39
|
+
const isSuccess = () => _status === STATUS.SUCCESS;
|
|
40
|
+
const isFailure = () => _status === STATUS.FAILURE;
|
|
41
|
+
const isCustom = () => !_builtinStatuses.has(_status);
|
|
42
|
+
const initial = () => _set(STATUS.INITIAL);
|
|
43
|
+
const pending = () => _set(STATUS.PENDING);
|
|
44
|
+
const success = () => _set(STATUS.SUCCESS);
|
|
45
|
+
const failure = (error = null) => _set(STATUS.FAILURE, error);
|
|
46
|
+
const custom = (type) => {
|
|
47
|
+
if (!type) {
|
|
48
|
+
console.error(`${_prefix} custom() requires a non-empty string`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
_set(type);
|
|
52
|
+
};
|
|
53
|
+
const lock = () => {
|
|
54
|
+
_locked = true;
|
|
55
|
+
};
|
|
56
|
+
const unlock = () => {
|
|
57
|
+
_locked = false;
|
|
58
|
+
};
|
|
59
|
+
const subscribe = (fn) => {
|
|
60
|
+
if (_destroyed) return () => {
|
|
61
|
+
};
|
|
62
|
+
_listeners.add(fn);
|
|
63
|
+
return () => _listeners.delete(fn);
|
|
64
|
+
};
|
|
65
|
+
const watch = (types, fn) => {
|
|
66
|
+
const _types = Array.isArray(types) ? types : [types];
|
|
67
|
+
return subscribe((curr, prev) => {
|
|
68
|
+
if (_types.includes(curr.status) || _types.includes(prev.status))
|
|
69
|
+
fn(curr, prev);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const when = (type, fn) => {
|
|
73
|
+
return subscribe((curr, prev) => {
|
|
74
|
+
if (curr.status === type && prev.status !== type) fn(curr, prev);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
const wait = (types = [STATUS.SUCCESS, STATUS.FAILURE], timeoutSec = 10) => {
|
|
78
|
+
const _types = typeof types === "string" ? [types] : types;
|
|
79
|
+
if (_destroyed) {
|
|
80
|
+
return Promise.resolve({
|
|
81
|
+
timeout: false,
|
|
82
|
+
immediate: true,
|
|
83
|
+
destroyed: true,
|
|
84
|
+
status: _status,
|
|
85
|
+
error: _error
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
if (_types.includes(_status)) {
|
|
90
|
+
resolve({
|
|
91
|
+
timeout: false,
|
|
92
|
+
immediate: true,
|
|
93
|
+
status: _status,
|
|
94
|
+
error: _error
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let unsubscribe;
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
unsubscribe();
|
|
101
|
+
resolve({
|
|
102
|
+
timeout: true,
|
|
103
|
+
immediate: false,
|
|
104
|
+
status: _status,
|
|
105
|
+
error: _error
|
|
106
|
+
});
|
|
107
|
+
}, timeoutSec * 1e3);
|
|
108
|
+
unsubscribe = subscribe(({ status, error }) => {
|
|
109
|
+
if (_types.includes(status)) {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
unsubscribe();
|
|
112
|
+
resolve({
|
|
113
|
+
timeout: false,
|
|
114
|
+
immediate: false,
|
|
115
|
+
status,
|
|
116
|
+
error
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const reset = () => {
|
|
123
|
+
if (_destroyed) return;
|
|
124
|
+
_locked = false;
|
|
125
|
+
_set(STATUS.INITIAL);
|
|
126
|
+
};
|
|
127
|
+
const destroy = () => {
|
|
128
|
+
_listeners.clear();
|
|
129
|
+
_destroyed = true;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
get status() {
|
|
133
|
+
return _status;
|
|
134
|
+
},
|
|
135
|
+
get error() {
|
|
136
|
+
return _error;
|
|
137
|
+
},
|
|
138
|
+
get isInitial() {
|
|
139
|
+
return isInitial();
|
|
140
|
+
},
|
|
141
|
+
get isPending() {
|
|
142
|
+
return isPending();
|
|
143
|
+
},
|
|
144
|
+
get isSuccess() {
|
|
145
|
+
return isSuccess();
|
|
146
|
+
},
|
|
147
|
+
get isFailure() {
|
|
148
|
+
return isFailure();
|
|
149
|
+
},
|
|
150
|
+
get isCustom() {
|
|
151
|
+
return isCustom();
|
|
152
|
+
},
|
|
153
|
+
get isLocked() {
|
|
154
|
+
return _locked;
|
|
155
|
+
},
|
|
156
|
+
initial,
|
|
157
|
+
pending,
|
|
158
|
+
success,
|
|
159
|
+
failure,
|
|
160
|
+
custom,
|
|
161
|
+
lock,
|
|
162
|
+
unlock,
|
|
163
|
+
subscribe,
|
|
164
|
+
watch,
|
|
165
|
+
when,
|
|
166
|
+
wait,
|
|
167
|
+
reset,
|
|
168
|
+
destroy
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
var index_default = astatus;
|
|
172
|
+
export {
|
|
173
|
+
STATUS,
|
|
174
|
+
index_default as default
|
|
175
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "astatus",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Async status signal. No data, no fetch — just the stage.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
21
|
+
"test": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"async",
|
|
25
|
+
"status",
|
|
26
|
+
"state",
|
|
27
|
+
"signal",
|
|
28
|
+
"framework-agnostic"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "latest",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vitest": "latest"
|
|
35
|
+
}
|
|
36
|
+
}
|