@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -31,82 +31,82 @@ import { randomUUID } from "node:crypto";
|
|
|
31
31
|
export type MessageRole = "user" | "assistant";
|
|
32
32
|
|
|
33
33
|
export interface TextBlock {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
type: "text";
|
|
35
|
+
text: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface ToolUseBlock {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
type: "tool_use";
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
input: Record<string, unknown>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export interface ToolResultBlock {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
type: "tool_result";
|
|
47
|
+
tool_use_id: string;
|
|
48
|
+
content: string | Array<TextBlock | ImageBlock>;
|
|
49
|
+
is_error?: boolean;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface ImageBlock {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
type: "image";
|
|
54
|
+
source: {
|
|
55
|
+
type: "base64";
|
|
56
|
+
media_type: string;
|
|
57
|
+
data: string;
|
|
58
|
+
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export type MessageContent = string | Array<TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock>;
|
|
62
62
|
|
|
63
63
|
export interface Message {
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
role: MessageRole;
|
|
65
|
+
content: MessageContent;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface Conversation {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
messages: Message[];
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export interface ConversationFactory {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
/** Generate unique IDs (default: true) */
|
|
75
|
+
generateIds?: boolean;
|
|
76
|
+
/** Default prefix for generated IDs */
|
|
77
|
+
idPrefix?: string;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export interface MakeConversationOptions extends ConversationFactory {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
/** Pre-populated messages array */
|
|
82
|
+
messages?: Message[];
|
|
83
|
+
/** Optional conversation metadata */
|
|
84
|
+
metadata?: Record<string, unknown>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export interface MakeMessageOptions {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
/** Message role (user or assistant) */
|
|
89
|
+
role?: MessageRole;
|
|
90
|
+
/** Message content - string or content blocks array */
|
|
91
|
+
content?: MessageContent;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export interface MakeToolUseOptions {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
/** Unique tool use ID (auto-generated if not provided) */
|
|
96
|
+
id?: string;
|
|
97
|
+
/** Tool name */
|
|
98
|
+
name?: string;
|
|
99
|
+
/** Tool input parameters */
|
|
100
|
+
input?: Record<string, unknown>;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export interface MakeToolResultOptions {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
/** ID of the tool_use this result corresponds to */
|
|
105
|
+
toolUseId?: string;
|
|
106
|
+
/** Result content - string or content blocks */
|
|
107
|
+
content?: string | Array<TextBlock | ImageBlock>;
|
|
108
|
+
/** Whether this result represents an error */
|
|
109
|
+
isError?: boolean;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
@@ -122,14 +122,14 @@ let idCounter = 0;
|
|
|
122
122
|
* @returns Unique ID string
|
|
123
123
|
*/
|
|
124
124
|
export function generateToolUseId(prefix = "tu"): string {
|
|
125
|
-
|
|
125
|
+
return `${prefix}_${randomUUID().replace(/-/g, "").slice(0, 12)}_${++idCounter}`;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
129
|
* Reset the ID counter for deterministic tests.
|
|
130
130
|
*/
|
|
131
131
|
export function resetIdCounter(): void {
|
|
132
|
-
|
|
132
|
+
idCounter = 0;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// ---------------------------------------------------------------------------
|
|
@@ -151,10 +151,10 @@ export function resetIdCounter(): void {
|
|
|
151
151
|
* ```
|
|
152
152
|
*/
|
|
153
153
|
export function makeConversation(opts: MakeConversationOptions = {}): Conversation {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
return {
|
|
155
|
+
messages: opts.messages ?? [],
|
|
156
|
+
metadata: opts.metadata,
|
|
157
|
+
};
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
@@ -173,10 +173,10 @@ export function makeConversation(opts: MakeConversationOptions = {}): Conversati
|
|
|
173
173
|
* ```
|
|
174
174
|
*/
|
|
175
175
|
export function makeMessage(opts: MakeMessageOptions = {}): Message {
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const role = opts.role ?? "user";
|
|
177
|
+
const content = opts.content ?? "";
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
return { role, content };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/**
|
|
@@ -186,7 +186,7 @@ export function makeMessage(opts: MakeMessageOptions = {}): Message {
|
|
|
186
186
|
* @returns TextBlock object
|
|
187
187
|
*/
|
|
188
188
|
export function makeTextBlock(text: string): TextBlock {
|
|
189
|
-
|
|
189
|
+
return { type: "text", text };
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/**
|
|
@@ -204,12 +204,12 @@ export function makeTextBlock(text: string): TextBlock {
|
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
export function makeToolUse(opts: MakeToolUseOptions = {}): ToolUseBlock {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
207
|
+
return {
|
|
208
|
+
type: "tool_use",
|
|
209
|
+
id: opts.id ?? generateToolUseId(),
|
|
210
|
+
name: opts.name ?? "unnamed_tool",
|
|
211
|
+
input: opts.input ?? {},
|
|
212
|
+
};
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
/**
|
|
@@ -227,12 +227,12 @@ export function makeToolUse(opts: MakeToolUseOptions = {}): ToolUseBlock {
|
|
|
227
227
|
* ```
|
|
228
228
|
*/
|
|
229
229
|
export function makeToolResult(opts: MakeToolResultOptions = {}): ToolResultBlock {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
return {
|
|
231
|
+
type: "tool_result",
|
|
232
|
+
tool_use_id: opts.toolUseId ?? generateToolUseId("tr"),
|
|
233
|
+
content: opts.content ?? "",
|
|
234
|
+
is_error: opts.isError ?? false,
|
|
235
|
+
};
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
// ---------------------------------------------------------------------------
|
|
@@ -247,7 +247,7 @@ export function makeToolResult(opts: MakeToolResultOptions = {}): ToolResultBloc
|
|
|
247
247
|
* @returns True if the pair is valid
|
|
248
248
|
*/
|
|
249
249
|
export function validateToolPair(toolUse: ToolUseBlock, toolResult: ToolResultBlock): boolean {
|
|
250
|
-
|
|
250
|
+
return toolUse.id === toolResult.tool_use_id;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/**
|
|
@@ -258,16 +258,16 @@ export function validateToolPair(toolUse: ToolUseBlock, toolResult: ToolResultBl
|
|
|
258
258
|
* @returns The matching ToolResultBlock or undefined
|
|
259
259
|
*/
|
|
260
260
|
export function findToolResult(messages: Message[], toolUseId: string): ToolResultBlock | undefined {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
for (const message of messages) {
|
|
262
|
+
if (typeof message.content === "string") continue;
|
|
263
|
+
|
|
264
|
+
for (const block of message.content) {
|
|
265
|
+
if (block.type === "tool_result" && block.tool_use_id === toolUseId) {
|
|
266
|
+
return block;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
268
269
|
}
|
|
269
|
-
|
|
270
|
-
return undefined;
|
|
270
|
+
return undefined;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
@@ -277,35 +277,35 @@ export function findToolResult(messages: Message[], toolUseId: string): ToolResu
|
|
|
277
277
|
* @returns Object with validation results
|
|
278
278
|
*/
|
|
279
279
|
export function validateConversationTools(conversation: Conversation): {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
valid: boolean;
|
|
281
|
+
unmatchedToolUses: ToolUseBlock[];
|
|
282
|
+
unmatchedToolResults: ToolResultBlock[];
|
|
283
283
|
} {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
284
|
+
const toolUses: ToolUseBlock[] = [];
|
|
285
|
+
const toolResults: ToolResultBlock[] = [];
|
|
286
|
+
|
|
287
|
+
// Collect all tool_use and tool_result blocks
|
|
288
|
+
for (const message of conversation.messages) {
|
|
289
|
+
if (typeof message.content === "string") continue;
|
|
290
|
+
|
|
291
|
+
for (const block of message.content) {
|
|
292
|
+
if (block.type === "tool_use") {
|
|
293
|
+
toolUses.push(block);
|
|
294
|
+
} else if (block.type === "tool_result") {
|
|
295
|
+
toolResults.push(block);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
297
298
|
}
|
|
298
|
-
}
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
const unmatchedToolUses = toolUses.filter((tu) => !toolResults.some((tr) => tr.tool_use_id === tu.id));
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
const unmatchedToolResults = toolResults.filter((tr) => !toolUses.some((tu) => tu.id === tr.tool_use_id));
|
|
303
303
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
304
|
+
return {
|
|
305
|
+
valid: unmatchedToolUses.length === 0 && unmatchedToolResults.length === 0,
|
|
306
|
+
unmatchedToolUses,
|
|
307
|
+
unmatchedToolResults,
|
|
308
|
+
};
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
// ---------------------------------------------------------------------------
|
|
@@ -330,13 +330,13 @@ export function validateConversationTools(conversation: Conversation): {
|
|
|
330
330
|
* ```
|
|
331
331
|
*/
|
|
332
332
|
export function makeToolExchange(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
toolName: string,
|
|
334
|
+
toolInput: Record<string, unknown>,
|
|
335
|
+
resultContent: string,
|
|
336
336
|
): [ToolUseBlock, ToolResultBlock] {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
const toolUse = makeToolUse({ name: toolName, input: toolInput });
|
|
338
|
+
const toolResult = makeToolResult({ toolUseId: toolUse.id, content: resultContent });
|
|
339
|
+
return [toolUse, toolResult];
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
/**
|
|
@@ -359,18 +359,18 @@ export function makeToolExchange(
|
|
|
359
359
|
* ```
|
|
360
360
|
*/
|
|
361
361
|
export function makeToolConversation(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
userPrompt: string,
|
|
363
|
+
toolName: string,
|
|
364
|
+
toolInput: Record<string, unknown>,
|
|
365
|
+
toolOutput: string,
|
|
366
366
|
): Conversation {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
367
|
+
const [toolUse, toolResult] = makeToolExchange(toolName, toolInput, toolOutput);
|
|
368
|
+
|
|
369
|
+
return makeConversation({
|
|
370
|
+
messages: [
|
|
371
|
+
makeMessage({ role: "user", content: userPrompt }),
|
|
372
|
+
makeMessage({ role: "assistant", content: [toolUse] }),
|
|
373
|
+
makeMessage({ role: "user", content: [toolResult] }),
|
|
374
|
+
],
|
|
375
|
+
});
|
|
376
376
|
}
|
|
@@ -2,160 +2,160 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { createDeferred, createDeferredQueue, nextTick } from "./deferred";
|
|
3
3
|
|
|
4
4
|
describe("deferred helpers", () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
describe("createDeferred", () => {
|
|
6
|
+
it("should resolve with expected value", async () => {
|
|
7
|
+
const deferred = createDeferred<string>();
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
deferred.resolve("expected-value");
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const result = await deferred.promise;
|
|
12
|
+
expect(result).toBe("expected-value");
|
|
13
|
+
});
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
it("should reject with expected reason", async () => {
|
|
16
|
+
const deferred = createDeferred<string>();
|
|
17
|
+
const error = new Error("test-error");
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
deferred.reject(error);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
await expect(deferred.promise).rejects.toBe(error);
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
it("should track settled state", async () => {
|
|
25
|
+
const deferred = createDeferred<string>();
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
expect(deferred.settled).toBe(false);
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
deferred.resolve("done");
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
expect(deferred.settled).toBe(true);
|
|
32
|
+
await deferred.promise;
|
|
33
|
+
});
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
it("should ignore second resolve", async () => {
|
|
36
|
+
const deferred = createDeferred<string>();
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
deferred.resolve("first");
|
|
39
|
+
deferred.resolve("second");
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const result = await deferred.promise;
|
|
42
|
+
expect(result).toBe("first");
|
|
43
|
+
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
it("should ignore reject after resolve", async () => {
|
|
46
|
+
const deferred = createDeferred<string>();
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
deferred.resolve("value");
|
|
49
|
+
deferred.reject(new Error("ignored"));
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const result = await deferred.promise;
|
|
52
|
+
expect(result).toBe("value");
|
|
53
|
+
});
|
|
53
54
|
});
|
|
54
|
-
});
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
describe("createDeferredQueue", () => {
|
|
57
|
+
it("should resolve deferreds in FIFO order", async () => {
|
|
58
|
+
const queue = createDeferredQueue<string>();
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const d1 = queue.enqueue();
|
|
61
|
+
const d2 = queue.enqueue();
|
|
62
|
+
const d3 = queue.enqueue();
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
expect(queue.pending).toBe(3);
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
queue.resolveNext("first");
|
|
67
|
+
queue.resolveNext("second");
|
|
68
|
+
queue.resolveNext("third");
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
expect(queue.pending).toBe(0);
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const results = await Promise.all([d1.promise, d2.promise, d3.promise]);
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
expect(results).toEqual(["first", "second", "third"]);
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
it("should reject deferreds in FIFO order", async () => {
|
|
78
|
+
const queue = createDeferredQueue<string>();
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const d1 = queue.enqueue();
|
|
81
|
+
const d2 = queue.enqueue();
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const error1 = new Error("error-1");
|
|
84
|
+
const error2 = new Error("error-2");
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
queue.rejectNext(error1);
|
|
87
|
+
queue.rejectNext(error2);
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
await expect(d1.promise).rejects.toBe(error1);
|
|
90
|
+
await expect(d2.promise).rejects.toBe(error2);
|
|
91
|
+
});
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
it("should return false when resolving empty queue", () => {
|
|
94
|
+
const queue = createDeferredQueue<string>();
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
const result = queue.resolveNext("value");
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
expect(result).toBe(false);
|
|
99
|
+
});
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
it("should return false when rejecting empty queue", () => {
|
|
102
|
+
const queue = createDeferredQueue<string>();
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
const result = queue.rejectNext(new Error("test"));
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
expect(result).toBe(false);
|
|
107
|
+
});
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
it("should handle mixed resolve and reject", async () => {
|
|
110
|
+
const queue = createDeferredQueue<string>();
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const d1 = queue.enqueue();
|
|
113
|
+
const d2 = queue.enqueue();
|
|
114
|
+
const d3 = queue.enqueue();
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
queue.resolveNext("success");
|
|
117
|
+
queue.rejectNext(new Error("failure"));
|
|
118
|
+
queue.resolveNext("another-success");
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
const r1 = await d1.promise;
|
|
121
|
+
expect(r1).toBe("success");
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
await expect(d2.promise).rejects.toThrow("failure");
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
const r3 = await d3.promise;
|
|
126
|
+
expect(r3).toBe("another-success");
|
|
127
|
+
});
|
|
127
128
|
});
|
|
128
|
-
});
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
describe("nextTick", () => {
|
|
131
|
+
it("should allow pending promises to settle", async () => {
|
|
132
|
+
const deferred = createDeferred<string>();
|
|
133
|
+
let resolved = false;
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
deferred.promise.then(() => {
|
|
136
|
+
resolved = true;
|
|
137
|
+
});
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
deferred.resolve("done");
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
expect(resolved).toBe(false);
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
await nextTick();
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
expect(resolved).toBe(true);
|
|
146
|
+
});
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
it("should resolve after microtask queue", async () => {
|
|
149
|
+
const order: string[] = [];
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
Promise.resolve().then(() => order.push("promise-1"));
|
|
152
|
+
Promise.resolve().then(() => order.push("promise-2"));
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
await nextTick();
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
order.push("after-nextTick");
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
expect(order).toEqual(["promise-1", "promise-2", "after-nextTick"]);
|
|
159
|
+
});
|
|
159
160
|
});
|
|
160
|
-
});
|
|
161
161
|
});
|