@flex-development/when 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @file createThenable
3
+ * @module when/testing/lib/createThenable
4
+ */
5
+ import isThenable from '#lib/is-thenable';
6
+ export default createThenable;
7
+ /**
8
+ * Create a thenable.
9
+ *
10
+ * The returned object conforms to {@linkcode Thenable} and ensures `then`
11
+ * always returns another {@linkcode Thenable}, even when adopting a foreign
12
+ * thenable.
13
+ *
14
+ * When `options` is omitted, `null`, or `undefined`, the returned thenable is
15
+ * *modern* (a thenable with `then`, `catch`, and `finally` methods).
16
+ * Pass an options object (e.g. `{}`) to start from a *bare* (`then` method
17
+ * only) thenable and selectively enable methods.
18
+ *
19
+ * @see {@linkcode CreateThenableOptions}
20
+ * @see {@linkcode Executor}
21
+ * @see {@linkcode Thenable}
22
+ *
23
+ * @template {any} T
24
+ * The resolved value
25
+ * @template {any} [Reason=Error]
26
+ * The reason for a rejection
27
+ * @template {Thenable<T>} [Result=Thenable<T>]
28
+ * The thenable
29
+ *
30
+ * @this {void}
31
+ *
32
+ * @param {Executor<T, Reason>} executor
33
+ * The initialization callback
34
+ * @param {CreateThenableOptions | null | undefined} [options]
35
+ * Options for creating a thenable
36
+ * @return {Result}
37
+ * The thenable
38
+ */
39
+ function createThenable(executor, options) {
40
+ /**
41
+ * Whether the thenable has been settled.
42
+ *
43
+ * @var {boolean} settled
44
+ */
45
+ let settled = false;
46
+ /**
47
+ * The settled state.
48
+ *
49
+ * @var {State<T, Reason> | undefined} state
50
+ */
51
+ let state;
52
+ try {
53
+ executor(ok, nok);
54
+ }
55
+ catch (e) {
56
+ nok(e);
57
+ }
58
+ // executor neither resolved nor rejected, treat as `ok(undefined)`.
59
+ if (!state)
60
+ ok(undefined);
61
+ return wrapState(state);
62
+ /**
63
+ * Adopt an awaitable into a {@linkcode Thenable}.
64
+ *
65
+ * If `x` is a thenable, the returned thenable follows its settlement.
66
+ * Otherwise, the returned thenable is immediately resolved with `x`.
67
+ *
68
+ * @template {any} X
69
+ * The resolved value
70
+ *
71
+ * @this {void}
72
+ *
73
+ * @param {Awaitable<X>} x
74
+ * The awaitable to adopt
75
+ * @return {Thenable<X>}
76
+ * A thenable representing the adopted awaitable
77
+ */
78
+ function adopt(x) {
79
+ if (isThenable(x)) {
80
+ // wrap foreign thenable so `then` still returns a thenable.
81
+ return addMethods({
82
+ /**
83
+ * @template {any} [Next=X]
84
+ * The next resolved value on success
85
+ * @template {any} [Failure=never]
86
+ * The next resolved value on failure
87
+ *
88
+ * @this {void}
89
+ *
90
+ * @param {OnFulfilled<X, Next> | null | undefined} [onfulfilled]
91
+ * The callback to execute when the thenable is resolved
92
+ * @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
93
+ * The callback to execute when the thenable is rejected
94
+ * @return {Thenable<Failure | Next>}
95
+ * The next thenable
96
+ */
97
+ then(onfulfilled, onrejected) {
98
+ return adopt(x.then(
99
+ /**
100
+ * @this {void}
101
+ *
102
+ * @param {X} value
103
+ * The resolved value
104
+ * @return {Awaitable<Next>}
105
+ * The next awaitable
106
+ */
107
+ function succ(value) {
108
+ return resolve(value, onfulfilled);
109
+ },
110
+ /**
111
+ * @this {void}
112
+ *
113
+ * @param {unknown} e
114
+ * The reason for the rejection
115
+ * @return {Awaitable<Failure>}
116
+ * The next awaitable
117
+ */
118
+ function fail(e) {
119
+ return reject(e, onrejected);
120
+ }));
121
+ }
122
+ });
123
+ }
124
+ return wrapState({ ok: true, value: x });
125
+ }
126
+ /**
127
+ * Add requested methods to a `thenable`.
128
+ *
129
+ * @template {any} U
130
+ * The resolved value
131
+ *
132
+ * @this {void}
133
+ *
134
+ * @param {Thenable<U>} thenable
135
+ * The current thenable
136
+ * @return {Thenable<U>}
137
+ * The `thenable`
138
+ */
139
+ function addMethods(thenable) {
140
+ if (!options) {
141
+ thenable.catch = katch;
142
+ thenable.finally = finalize;
143
+ return thenable;
144
+ }
145
+ if (options.catch) {
146
+ thenable.catch = katch;
147
+ }
148
+ else if ('catch' in options && options.catch !== false) {
149
+ thenable.catch = options.catch;
150
+ }
151
+ if (options.finally) {
152
+ thenable.finally = finalize;
153
+ }
154
+ else if ('finally' in options && options.finally !== false) {
155
+ thenable.finally = options.finally;
156
+ }
157
+ return thenable;
158
+ }
159
+ /**
160
+ * Attach a callback that is invoked only when `this` thenable is settled.
161
+ *
162
+ * @template {any} U
163
+ * The resolved value
164
+ *
165
+ * @this {Thenable<U>}
166
+ *
167
+ * @param {OnFinally | null | undefined} [onfinally]
168
+ * The callback to execute when the thenable is settled
169
+ * @return {Thenable<U>}
170
+ * The next thenable
171
+ */
172
+ function finalize(onfinally) {
173
+ return typeof onfinally === 'function' ? this.then(succ, fail) : this;
174
+ /**
175
+ * @this {void}
176
+ *
177
+ * @param {unknown} reason
178
+ * The reason for the rejection
179
+ * @return {never}
180
+ * Never; throws `reason`
181
+ * @throws {unknown}
182
+ */
183
+ function fail(reason) {
184
+ void onfinally();
185
+ throw reason;
186
+ }
187
+ /**
188
+ * @this {void}
189
+ *
190
+ * @param {U} value
191
+ * The resolved value
192
+ * @return {U}
193
+ * The resolved `value`
194
+ */
195
+ function succ(value) {
196
+ return void onfinally(), value;
197
+ }
198
+ }
199
+ /**
200
+ * Attach a callback only for the rejection of `this` thenable.
201
+ *
202
+ * @template {any} U
203
+ * The resolved value
204
+ * @template {any} [Failure=never]
205
+ * The resolved value on failure
206
+ *
207
+ * @this {Thenable<U>}
208
+ *
209
+ * @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
210
+ * The callback to execute when the thenable is rejected
211
+ * @return {Thenable<Failure | U>}
212
+ * The next thenable
213
+ */
214
+ function katch(onrejected) {
215
+ return this.then(null, onrejected);
216
+ }
217
+ /**
218
+ * Reject the thenable.
219
+ *
220
+ * @this {void}
221
+ *
222
+ * @param {Reason} reason
223
+ * The reason for the rejection
224
+ * @return {undefined}
225
+ */
226
+ function nok(reason) {
227
+ return void (settled || (settled = true, state = { ok: false, reason }));
228
+ }
229
+ /**
230
+ * Resolve the thenable.
231
+ *
232
+ * @this {void}
233
+ *
234
+ * @param {Awaitable<T>} value
235
+ * The awaitable
236
+ * @return {undefined}
237
+ */
238
+ function ok(value) {
239
+ return void (settled || (settled = true, state = { ok: true, value }));
240
+ }
241
+ /**
242
+ * Handle a rejection.
243
+ *
244
+ * @template {any} T
245
+ * The next resolved value
246
+ *
247
+ * @this {void}
248
+ *
249
+ * @param {unknown} reason
250
+ * The reason for the rejection
251
+ * @param {OnRejected<T, Reason> | null | undefined} [onrejected]
252
+ * The callback to execute when the thenable is rejected
253
+ * @return {Awaitable<T>}
254
+ * The next awaitable
255
+ */
256
+ function reject(reason, onrejected) {
257
+ if (typeof onrejected === 'function')
258
+ return onrejected(reason);
259
+ return wrapState({ ok: false, reason: reason });
260
+ }
261
+ /**
262
+ * Handle a resolved `value`.
263
+ *
264
+ * @template {any} T
265
+ * The resolved value
266
+ * @template {any} Next
267
+ * The next resolved value
268
+ * @this {void}
269
+ *
270
+ * @param {T} value
271
+ * The resolved value
272
+ * @return {Awaitable<Next>}
273
+ * The next awaitable
274
+ */
275
+ function resolve(value, onfulfilled) {
276
+ if (typeof onfulfilled === 'function')
277
+ return onfulfilled(value);
278
+ return value;
279
+ }
280
+ /**
281
+ * Wrap a settled state into a {@linkcode Thenable}.
282
+ *
283
+ * @template {any} S
284
+ * The resolved value
285
+ *
286
+ * @this {void}
287
+ *
288
+ * @param {State} s
289
+ * The settled state
290
+ * @return {Thenable<S>}
291
+ * A thenable representing the settled state
292
+ */
293
+ function wrapState(s) {
294
+ return addMethods({
295
+ /**
296
+ * @template {any} [Next=S]
297
+ * The next resolved value on success
298
+ * @template {any} [Failure=never]
299
+ * The next resolved value on failure
300
+ *
301
+ * @this {void}
302
+ *
303
+ * @param {OnFulfilled<S, Next> | null | undefined} [onfulfilled]
304
+ * The callback to execute when the thenable is resolved
305
+ * @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
306
+ * The callback to execute when the thenable is rejected
307
+ * @return {Thenable<Failure | Next>}
308
+ * The next thenable
309
+ */
310
+ then(onfulfilled, onrejected) {
311
+ try {
312
+ if (s.ok) {
313
+ if (isThenable(s.value))
314
+ return adopt(s.value).then(succ, fail);
315
+ return adopt(succ(s.value));
316
+ /**
317
+ * @this {void}
318
+ *
319
+ * @param {S} value
320
+ * The resolved value
321
+ * @return {Awaitable<Next>}
322
+ * The next awaitable
323
+ */
324
+ function succ(value) {
325
+ return resolve(value, onfulfilled);
326
+ }
327
+ }
328
+ // rejected.
329
+ if (typeof onrejected === 'function')
330
+ return adopt(fail(s.reason));
331
+ return wrapState({ ok: false, reason: s.reason });
332
+ }
333
+ catch (e) {
334
+ return wrapState({ ok: false, reason: e });
335
+ }
336
+ /**
337
+ * @this {void}
338
+ *
339
+ * @param {unknown} e
340
+ * The reason for the rejection
341
+ * @return {Awaitable<Failure>}
342
+ * The next awaitable
343
+ */
344
+ function fail(e) {
345
+ return reject(e, onrejected);
346
+ }
347
+ }
348
+ });
349
+ }
350
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file Entry Point - Library
3
+ * @module when/testing/lib
4
+ */
5
+ export { default as createThenable } from '#testing/lib/create-thenable';
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@flex-development/when",
3
- "description": "Like .then, but for synchronous values and thenables",
4
- "version": "1.0.0",
3
+ "description": "Chain callbacks on synchronous values or thenables without forcing Promise.resolve or microtasks.",
4
+ "version": "3.0.0",
5
5
  "keywords": [
6
6
  "async",
7
7
  "await",
8
8
  "awaitable",
9
9
  "callback",
10
10
  "chain",
11
+ "esm",
12
+ "functional",
13
+ "hooks",
14
+ "microtask",
15
+ "pipeline",
11
16
  "promise",
17
+ "promise-like",
12
18
  "sync",
13
19
  "synchronous",
14
20
  "then",
15
21
  "thenable",
16
- "typescript"
22
+ "typescript",
23
+ "utility"
17
24
  ],
18
25
  "license": "BSD-3-Clause",
19
26
  "homepage": "https://github.com/flex-development/when",
@@ -48,7 +55,11 @@
48
55
  "when": "./src/index.mts",
49
56
  "default": "./dist/index.mjs"
50
57
  },
51
- "./package.json": "./package.json"
58
+ "./package.json": "./package.json",
59
+ "./testing": {
60
+ "when": "./src/testing/index.mts",
61
+ "default": "./dist/testing/index.mjs"
62
+ }
52
63
  },
53
64
  "imports": {
54
65
  "#fixtures/*": "./__fixtures__/*.mts",
@@ -60,6 +71,10 @@
60
71
  "when": "./src/lib/*.mts",
61
72
  "default": "./dist/lib/*.mjs"
62
73
  },
74
+ "#testing/*": {
75
+ "when": "./src/testing/*.mts",
76
+ "default": "./dist/testing/*.mjs"
77
+ },
63
78
  "#tests/*": "./__tests__/*.mts",
64
79
  "#types/*": {
65
80
  "when": "./src/types/*.mts",
@@ -75,7 +90,7 @@
75
90
  },
76
91
  "scripts": {
77
92
  "build": "yarn clean:build && tsc -p tsconfig.build.json --noEmit false && yarn bundle:dts",
78
- "bundle:dts": "rollup --config=rollup.config.mts && trash dist/{interfaces,types} && trash dist/{internal,lib}/*.d.mts",
93
+ "bundle:dts": "rollup --config=rollup.config.mts && trash dist/**/{interfaces,types} && trash dist/**/lib/*.d.mts",
79
94
  "check:ci": "yarn dedupe --check && yarn check:format && yarn check:lint && yarn check:spelling && yarn typecheck && yarn check:types && yarn test:cov && yarn pack && yarn check:types:build && attw package.tgz && yarn clean:pack",
80
95
  "check:format": "dprint check --incremental=false",
81
96
  "check:lint": "eslint --exit-on-fatal-error --max-warnings 0 .",
@@ -114,9 +129,10 @@
114
129
  },
115
130
  "devDependencies": {
116
131
  "@arethetypeswrong/cli": "0.18.2",
117
- "@commitlint/cli": "20.4.1",
132
+ "@commitlint/cli": "20.4.2",
118
133
  "@commitlint/types": "20.4.0",
119
134
  "@edge-runtime/vm": "5.0.0",
135
+ "@faker-js/faker": "10.3.0",
120
136
  "@flex-development/commitlint-config": "1.0.1",
121
137
  "@flex-development/eslint-config": "1.1.1",
122
138
  "@flex-development/grease": "3.0.0-alpha.9",
@@ -134,20 +150,20 @@
134
150
  "@vitest/ui": "4.0.18",
135
151
  "concat-stream": "2.0.0",
136
152
  "cross-env": "10.1.0",
137
- "cspell": "9.6.4",
153
+ "cspell": "9.7.0",
138
154
  "devlop": "1.1.0",
139
155
  "dprint": "0.51.1",
140
156
  "editorconfig": "3.0.1",
141
157
  "eslint": "9.39.2",
142
158
  "growl": "1.10.5",
143
- "happy-dom": "20.6.2",
159
+ "happy-dom": "20.7.0",
144
160
  "husky": "9.1.7",
145
161
  "is-ci": "4.1.0",
146
162
  "node-notifier": "10.0.1",
147
163
  "pkg-size": "2.4.0",
148
164
  "remark": "15.0.1",
149
165
  "remark-cli": "12.0.1",
150
- "rollup": "4.57.1",
166
+ "rollup": "4.59.0",
151
167
  "rollup-plugin-dts": "6.3.0",
152
168
  "sh-syntax": "0.5.8",
153
169
  "trash-cli": "7.2.0",