kernl 0.2.1 → 0.6.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +4 -0
- package/CHANGELOG.md +138 -0
- package/LICENSE +1 -1
- package/dist/agent/__tests__/concurrency.test.d.ts +2 -0
- package/dist/agent/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/agent/__tests__/concurrency.test.js +152 -0
- package/dist/agent/__tests__/run.test.d.ts +2 -0
- package/dist/agent/__tests__/run.test.d.ts.map +1 -0
- package/dist/agent/__tests__/run.test.js +357 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent.d.ts +32 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +101 -14
- package/dist/api/__tests__/cursor-page.test.d.ts +2 -0
- package/dist/api/__tests__/cursor-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/cursor-page.test.js +414 -0
- package/dist/api/__tests__/offset-page.test.d.ts +2 -0
- package/dist/api/__tests__/offset-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/offset-page.test.js +510 -0
- package/dist/api/__tests__/threads.test.d.ts +2 -0
- package/dist/api/__tests__/threads.test.d.ts.map +1 -0
- package/dist/api/__tests__/threads.test.js +338 -0
- package/dist/api/models/index.d.ts +2 -0
- package/dist/api/models/index.d.ts.map +1 -0
- package/dist/api/models/thread.d.ts +120 -0
- package/dist/api/models/thread.d.ts.map +1 -0
- package/dist/api/pagination/base.d.ts +48 -0
- package/dist/api/pagination/base.d.ts.map +1 -0
- package/dist/api/pagination/base.js +45 -0
- package/dist/api/pagination/cursor.d.ts +44 -0
- package/dist/api/pagination/cursor.d.ts.map +1 -0
- package/dist/api/pagination/cursor.js +52 -0
- package/dist/api/pagination/offset.d.ts +42 -0
- package/dist/api/pagination/offset.d.ts.map +1 -0
- package/dist/api/pagination/offset.js +55 -0
- package/dist/api/resources/threads/events.d.ts +21 -0
- package/dist/api/resources/threads/events.d.ts.map +1 -0
- package/dist/api/resources/threads/events.js +24 -0
- package/dist/api/resources/threads/index.d.ts +4 -0
- package/dist/api/resources/threads/index.d.ts.map +1 -0
- package/dist/api/resources/threads/index.js +2 -0
- package/dist/api/resources/threads/threads.d.ts +57 -0
- package/dist/api/resources/threads/threads.d.ts.map +1 -0
- package/dist/api/resources/threads/threads.js +199 -0
- package/dist/api/resources/threads/types.d.ts +123 -0
- package/dist/api/resources/threads/types.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.d.ts +18 -0
- package/dist/api/resources/threads/utils.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.js +78 -0
- package/dist/context.d.ts +5 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +2 -0
- package/dist/kernl/index.d.ts +3 -0
- package/dist/kernl/index.d.ts.map +1 -0
- package/dist/kernl/index.js +2 -0
- package/dist/kernl/kernl.d.ts +64 -0
- package/dist/kernl/kernl.d.ts.map +1 -0
- package/dist/kernl/kernl.js +116 -0
- package/dist/kernl/threads.d.ts +110 -0
- package/dist/kernl/threads.d.ts.map +1 -0
- package/dist/kernl/threads.js +126 -0
- package/dist/kernl.d.ts +22 -6
- package/dist/kernl.d.ts.map +1 -1
- package/dist/kernl.js +73 -10
- package/dist/lib/env.d.ts +3 -3
- package/dist/lib/env.js +1 -1
- package/dist/mcp/__tests__/integration.test.js +8 -8
- package/dist/mcp/__tests__/utils.test.js +6 -6
- package/dist/mcp/http.d.ts +1 -1
- package/dist/mcp/http.d.ts.map +1 -1
- package/dist/mcp/http.js +9 -9
- package/dist/mcp/sse.d.ts +1 -1
- package/dist/mcp/sse.d.ts.map +1 -1
- package/dist/mcp/sse.js +7 -7
- package/dist/mcp/utils.d.ts +1 -1
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +4 -5
- package/dist/storage/__tests__/in-memory.test.d.ts +2 -0
- package/dist/storage/__tests__/in-memory.test.d.ts.map +1 -0
- package/dist/storage/__tests__/in-memory.test.js +455 -0
- package/dist/storage/base.d.ts +64 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/base.js +4 -0
- package/dist/storage/in-memory.d.ts +62 -0
- package/dist/storage/in-memory.d.ts.map +1 -0
- package/dist/storage/in-memory.js +283 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/thread.d.ts +123 -0
- package/dist/storage/thread.d.ts.map +1 -0
- package/dist/storage/thread.js +4 -0
- package/dist/task.d.ts +5 -3
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +10 -8
- package/dist/thread/__tests__/fixtures/mock-model.d.ts +1 -2
- package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -1
- package/dist/thread/__tests__/integration.test.js +6 -6
- package/dist/thread/__tests__/namespace.test.d.ts +2 -0
- package/dist/thread/__tests__/namespace.test.d.ts.map +1 -0
- package/dist/thread/__tests__/namespace.test.js +131 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts +2 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts.map +1 -0
- package/dist/thread/__tests__/thread-persistence.test.js +351 -0
- package/dist/thread/__tests__/thread.test.js +49 -51
- package/dist/thread/thread.d.ts +70 -18
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +211 -73
- package/dist/thread/utils.d.ts +36 -8
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/utils.js +52 -8
- package/dist/tool/__tests__/fixtures.js +1 -1
- package/dist/tool/__tests__/toolkit.test.js +15 -12
- package/dist/tool/tool.js +3 -3
- package/dist/types/kernl.d.ts +42 -0
- package/dist/types/kernl.d.ts.map +1 -0
- package/dist/types/thread.d.ts +108 -22
- package/dist/types/thread.d.ts.map +1 -1
- package/dist/types/thread.js +12 -0
- package/package.json +11 -7
- package/src/agent/__tests__/concurrency.test.ts +194 -0
- package/src/agent/__tests__/run.test.ts +441 -0
- package/src/agent/index.ts +0 -0
- package/src/agent.ts +139 -24
- package/src/api/__tests__/cursor-page.test.ts +512 -0
- package/src/api/__tests__/offset-page.test.ts +624 -0
- package/src/api/__tests__/threads.test.ts +415 -0
- package/src/api/models/index.ts +6 -0
- package/src/api/models/thread.ts +138 -0
- package/src/api/pagination/base.ts +79 -0
- package/src/api/pagination/cursor.ts +86 -0
- package/src/api/pagination/offset.ts +89 -0
- package/src/api/resources/threads/events.ts +26 -0
- package/src/api/resources/threads/index.ts +9 -0
- package/src/api/resources/threads/threads.ts +256 -0
- package/src/api/resources/threads/types.ts +143 -0
- package/src/api/resources/threads/utils.ts +104 -0
- package/src/context.ts +10 -1
- package/src/index.ts +49 -1
- package/src/internal.ts +15 -0
- package/src/kernl.ts +86 -17
- package/src/mcp/__tests__/integration.test.ts +8 -9
- package/src/mcp/__tests__/utils.test.ts +6 -6
- package/src/mcp/http.ts +9 -9
- package/src/mcp/sse.ts +7 -7
- package/src/mcp/utils.ts +6 -5
- package/src/storage/__tests__/in-memory.test.ts +534 -0
- package/src/storage/base.ts +77 -0
- package/src/storage/in-memory.ts +372 -0
- package/src/storage/index.ts +21 -0
- package/src/storage/thread.ts +141 -0
- package/src/task.ts +12 -10
- package/src/thread/__tests__/fixtures/mock-model.ts +2 -4
- package/src/thread/__tests__/integration.test.ts +13 -12
- package/src/thread/__tests__/namespace.test.ts +158 -0
- package/src/thread/__tests__/thread-persistence.test.ts +367 -0
- package/src/thread/__tests__/thread.test.ts +52 -54
- package/src/thread/thread.ts +247 -96
- package/src/thread/utils.ts +76 -13
- package/src/tool/__tests__/fixtures.ts +1 -1
- package/src/tool/__tests__/toolkit.test.ts +15 -12
- package/src/tool/tool.ts +3 -3
- package/src/types/kernl.ts +51 -0
- package/src/types/thread.ts +139 -25
- package/vitest.config.ts +1 -0
- package/dist/env.d.ts +0 -45
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -31
- package/dist/error.d.ts +0 -1
- package/dist/error.d.ts.map +0 -1
- package/dist/kernel.d.ts +0 -7
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js +0 -7
- package/dist/lib/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/lib/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/lib/serde/__tests__/codec.test.js +0 -75
- package/dist/lib/serde/codec.d.ts +0 -12
- package/dist/lib/serde/codec.d.ts.map +0 -1
- package/dist/lib/serde/codec.js +0 -54
- package/dist/lib/serde/thread.d.ts +0 -1
- package/dist/lib/serde/thread.d.ts.map +0 -1
- package/dist/lib/serde/thread.js +0 -172
- package/dist/lib/serde/tool.d.ts +0 -36
- package/dist/lib/serde/tool.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -19
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -41
- package/dist/logger.d.ts +0 -36
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -43
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/echo-server.js +0 -92
- package/dist/mcp/__tests__/fixtures/math-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/math-server.js +0 -98
- package/dist/mcp/__tests__/fixtures/test-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/test-server.js +0 -163
- package/dist/mcp/__tests__/test-utils.d.ts +0 -17
- package/dist/mcp/__tests__/test-utils.d.ts.map +0 -1
- package/dist/mcp/__tests__/test-utils.js +0 -42
- package/dist/mcp/node.d.ts +0 -60
- package/dist/mcp/node.d.ts.map +0 -1
- package/dist/mcp/node.js +0 -297
- package/dist/model.d.ts +0 -175
- package/dist/model.d.ts.map +0 -1
- package/dist/providers/ai.d.ts +0 -1
- package/dist/providers/ai.d.ts.map +0 -1
- package/dist/providers/ai.js +0 -1
- package/dist/providers/default.d.ts +0 -16
- package/dist/providers/default.d.ts.map +0 -1
- package/dist/providers/default.js +0 -17
- package/dist/providers/registry.d.ts +0 -1
- package/dist/providers/registry.d.ts.map +0 -1
- package/dist/providers/registry.js +0 -1
- package/dist/sched/scheduler.d.ts +0 -20
- package/dist/sched/scheduler.d.ts.map +0 -1
- package/dist/sched/task.d.ts +0 -92
- package/dist/sched/task.d.ts.map +0 -1
- package/dist/sched/task.js +0 -102
- package/dist/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/serde/__tests__/codec.test.js +0 -75
- package/dist/serde/codec.d.ts +0 -12
- package/dist/serde/codec.d.ts.map +0 -1
- package/dist/serde/codec.js +0 -54
- package/dist/serde/json.d.ts +0 -8
- package/dist/serde/json.d.ts.map +0 -1
- package/dist/serde/json.js +0 -13
- package/dist/serde/thread.d.ts +0 -687
- package/dist/serde/thread.d.ts.map +0 -1
- package/dist/serde/thread.js +0 -158
- package/dist/serde/tool.d.ts +0 -36
- package/dist/serde/tool.d.ts.map +0 -1
- package/dist/session.d.ts +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -1
- package/dist/thread/__tests__/stream.test.d.ts +0 -2
- package/dist/thread/__tests__/stream.test.d.ts.map +0 -1
- package/dist/thread/__tests__/stream.test.js +0 -244
- package/dist/tool/mcp.d.ts +0 -75
- package/dist/tool/mcp.d.ts.map +0 -1
- package/dist/tool/mcp.js +0 -111
- package/dist/tools.d.ts +0 -362
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -220
- package/dist/types/proto.d.ts +0 -1551
- package/dist/types/proto.d.ts.map +0 -1
- package/dist/types/proto.js +0 -531
- package/dist/usage.d.ts +0 -43
- package/dist/usage.d.ts.map +0 -1
- package/dist/usage.js +0 -61
- package/src/lib/serde/thread.ts +0 -188
- /package/dist/{error.js → agent/index.js} +0 -0
- /package/dist/{lib/serde/tool.js → api/models/index.js} +0 -0
- /package/dist/{model.js → api/models/thread.js} +0 -0
- /package/dist/{sched/scheduler.js → api/resources/threads/types.js} +0 -0
- /package/dist/{serde/tool.js → types/kernl.js} +0 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
OffsetPage,
|
|
4
|
+
type OffsetPageResponse,
|
|
5
|
+
type OffsetPageParams,
|
|
6
|
+
} from "../pagination/offset";
|
|
7
|
+
|
|
8
|
+
describe("OffsetPage", () => {
|
|
9
|
+
describe("construction", () => {
|
|
10
|
+
it("should initialize with response data", () => {
|
|
11
|
+
const response: OffsetPageResponse<string> = {
|
|
12
|
+
data: ["a", "b", "c"],
|
|
13
|
+
offset: 0,
|
|
14
|
+
limit: 10,
|
|
15
|
+
total: 100,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const loader = vi.fn();
|
|
19
|
+
const page = new OffsetPage({
|
|
20
|
+
params: { offset: 0, limit: 10 },
|
|
21
|
+
response,
|
|
22
|
+
loader,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(page.data).toEqual(["a", "b", "c"]);
|
|
26
|
+
expect(page.items).toEqual(["a", "b", "c"]);
|
|
27
|
+
expect(page.offset).toBe(0);
|
|
28
|
+
expect(page.limit).toBe(10);
|
|
29
|
+
expect(page.total).toBe(100);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should handle empty data array", () => {
|
|
33
|
+
const response: OffsetPageResponse<string> = {
|
|
34
|
+
data: [],
|
|
35
|
+
offset: 0,
|
|
36
|
+
limit: 10,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const loader = vi.fn();
|
|
40
|
+
const page = new OffsetPage({
|
|
41
|
+
params: {},
|
|
42
|
+
response,
|
|
43
|
+
loader,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(page.data).toEqual([]);
|
|
47
|
+
expect(page.items).toEqual([]);
|
|
48
|
+
expect(page.last).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle null data gracefully", () => {
|
|
52
|
+
const response: OffsetPageResponse<string> = {
|
|
53
|
+
data: null as any,
|
|
54
|
+
offset: 0,
|
|
55
|
+
limit: 10,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const loader = vi.fn();
|
|
59
|
+
const page = new OffsetPage({
|
|
60
|
+
params: {},
|
|
61
|
+
response,
|
|
62
|
+
loader,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(page.data).toEqual([]);
|
|
66
|
+
expect(page.items).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should handle response without total", () => {
|
|
70
|
+
const response: OffsetPageResponse<string> = {
|
|
71
|
+
data: ["a", "b"],
|
|
72
|
+
offset: 0,
|
|
73
|
+
limit: 10,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const page = new OffsetPage({
|
|
77
|
+
params: {},
|
|
78
|
+
response,
|
|
79
|
+
loader: vi.fn(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(page.total).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("items getter", () => {
|
|
87
|
+
it("should return exact reference to data array", () => {
|
|
88
|
+
const data = ["a", "b", "c"];
|
|
89
|
+
const response: OffsetPageResponse<string> = {
|
|
90
|
+
data,
|
|
91
|
+
offset: 0,
|
|
92
|
+
limit: 10,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const page = new OffsetPage({
|
|
96
|
+
params: {},
|
|
97
|
+
response,
|
|
98
|
+
loader: vi.fn(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(page.items).toBe(page.data);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("last getter", () => {
|
|
106
|
+
it("should return true when data is empty", () => {
|
|
107
|
+
const page = new OffsetPage({
|
|
108
|
+
params: {},
|
|
109
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
110
|
+
loader: vi.fn(),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(page.last).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should return true when offset + limit >= total", () => {
|
|
117
|
+
const page = new OffsetPage({
|
|
118
|
+
params: {},
|
|
119
|
+
response: { data: ["a"], offset: 90, limit: 10, total: 100 },
|
|
120
|
+
loader: vi.fn(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(page.last).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should return true when offset + limit equals total exactly", () => {
|
|
127
|
+
const page = new OffsetPage({
|
|
128
|
+
params: {},
|
|
129
|
+
response: { data: ["a", "b"], offset: 98, limit: 2, total: 100 },
|
|
130
|
+
loader: vi.fn(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(page.last).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should return false when offset + limit < total", () => {
|
|
137
|
+
const page = new OffsetPage({
|
|
138
|
+
params: {},
|
|
139
|
+
response: { data: ["a", "b"], offset: 0, limit: 10, total: 100 },
|
|
140
|
+
loader: vi.fn(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(page.last).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should return false when total is undefined and data exists", () => {
|
|
147
|
+
const page = new OffsetPage({
|
|
148
|
+
params: {},
|
|
149
|
+
response: { data: ["a", "b"], offset: 0, limit: 10 },
|
|
150
|
+
loader: vi.fn(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(page.last).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should handle offset and limit of 0", () => {
|
|
157
|
+
const page = new OffsetPage({
|
|
158
|
+
params: {},
|
|
159
|
+
response: { data: ["a"], offset: 0, limit: 0 },
|
|
160
|
+
loader: vi.fn(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// With limit 0, next would be 0, so it can't progress
|
|
164
|
+
expect(page.last).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should handle undefined offset and limit", () => {
|
|
168
|
+
const page = new OffsetPage({
|
|
169
|
+
params: {},
|
|
170
|
+
response: {
|
|
171
|
+
data: ["a"],
|
|
172
|
+
offset: undefined as any,
|
|
173
|
+
limit: undefined as any,
|
|
174
|
+
},
|
|
175
|
+
loader: vi.fn(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(page.last).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("next()", () => {
|
|
183
|
+
it("should return null when last is true", async () => {
|
|
184
|
+
const page = new OffsetPage({
|
|
185
|
+
params: {},
|
|
186
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
187
|
+
loader: vi.fn(),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const nextPage = await page.next();
|
|
191
|
+
expect(nextPage).toBe(null);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should fetch next page with incremented offset", async () => {
|
|
195
|
+
const loader = vi.fn().mockResolvedValue({
|
|
196
|
+
data: ["d", "e", "f"],
|
|
197
|
+
offset: 10,
|
|
198
|
+
limit: 10,
|
|
199
|
+
total: 100,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const page = new OffsetPage({
|
|
203
|
+
params: { offset: 0, limit: 10 },
|
|
204
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 10, total: 100 },
|
|
205
|
+
loader,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const nextPage = await page.next();
|
|
209
|
+
|
|
210
|
+
expect(loader).toHaveBeenCalledTimes(1);
|
|
211
|
+
expect(loader).toHaveBeenCalledWith({
|
|
212
|
+
offset: 10,
|
|
213
|
+
limit: 10,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(nextPage).not.toBe(null);
|
|
217
|
+
expect(nextPage!.data).toEqual(["d", "e", "f"]);
|
|
218
|
+
expect(nextPage!.offset).toBe(10);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should calculate next offset correctly", async () => {
|
|
222
|
+
const loader = vi.fn().mockResolvedValue({
|
|
223
|
+
data: ["x"],
|
|
224
|
+
offset: 25,
|
|
225
|
+
limit: 5,
|
|
226
|
+
total: 100,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const page = new OffsetPage({
|
|
230
|
+
params: { offset: 20, limit: 5 },
|
|
231
|
+
response: { data: ["a"], offset: 20, limit: 5, total: 100 },
|
|
232
|
+
loader,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await page.next();
|
|
236
|
+
|
|
237
|
+
expect(loader).toHaveBeenCalledWith({
|
|
238
|
+
offset: 25,
|
|
239
|
+
limit: 5,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should preserve params across pagination", async () => {
|
|
244
|
+
const loader = vi.fn().mockResolvedValue({
|
|
245
|
+
data: ["x"],
|
|
246
|
+
offset: 10,
|
|
247
|
+
limit: 10,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const page = new OffsetPage({
|
|
251
|
+
params: { offset: 0, limit: 10 },
|
|
252
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
253
|
+
loader,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await page.next();
|
|
257
|
+
|
|
258
|
+
expect(loader).toHaveBeenCalledWith({
|
|
259
|
+
offset: 10,
|
|
260
|
+
limit: 10,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should return instance of OffsetPage", async () => {
|
|
265
|
+
const loader = vi.fn().mockResolvedValue({
|
|
266
|
+
data: ["d"],
|
|
267
|
+
offset: 10,
|
|
268
|
+
limit: 10,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const page = new OffsetPage({
|
|
272
|
+
params: { offset: 0, limit: 10 },
|
|
273
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
274
|
+
loader,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const nextPage = await page.next();
|
|
278
|
+
expect(nextPage).toBeInstanceOf(OffsetPage);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should handle undefined offset and limit", async () => {
|
|
282
|
+
const loader = vi.fn().mockResolvedValue({
|
|
283
|
+
data: ["b"],
|
|
284
|
+
offset: 0,
|
|
285
|
+
limit: 0,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const page = new OffsetPage({
|
|
289
|
+
params: {},
|
|
290
|
+
response: {
|
|
291
|
+
data: ["a"],
|
|
292
|
+
offset: undefined as any,
|
|
293
|
+
limit: undefined as any,
|
|
294
|
+
},
|
|
295
|
+
loader,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await page.next();
|
|
299
|
+
|
|
300
|
+
// offset ?? 0 + limit ?? 0 = 0
|
|
301
|
+
expect(loader).toHaveBeenCalledWith({
|
|
302
|
+
offset: 0,
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("pages() generator", () => {
|
|
308
|
+
it("should yield only current page when last", async () => {
|
|
309
|
+
const page = new OffsetPage({
|
|
310
|
+
params: {},
|
|
311
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
312
|
+
loader: vi.fn(),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const pages = [];
|
|
316
|
+
for await (const p of page.pages()) {
|
|
317
|
+
pages.push(p);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
expect(pages).toHaveLength(1);
|
|
321
|
+
expect(pages[0]).toBe(page);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("should yield multiple pages until last", async () => {
|
|
325
|
+
const loader = vi
|
|
326
|
+
.fn()
|
|
327
|
+
.mockResolvedValueOnce({
|
|
328
|
+
data: ["d", "e"],
|
|
329
|
+
offset: 3,
|
|
330
|
+
limit: 3,
|
|
331
|
+
total: 8,
|
|
332
|
+
})
|
|
333
|
+
.mockResolvedValueOnce({
|
|
334
|
+
data: ["f", "g"],
|
|
335
|
+
offset: 6,
|
|
336
|
+
limit: 3,
|
|
337
|
+
total: 8,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const page = new OffsetPage({
|
|
341
|
+
params: { offset: 0, limit: 3 },
|
|
342
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 3, total: 8 },
|
|
343
|
+
loader,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const pages = [];
|
|
347
|
+
for await (const p of page.pages()) {
|
|
348
|
+
pages.push(p);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
expect(pages).toHaveLength(3);
|
|
352
|
+
expect(pages[0].data).toEqual(["a", "b", "c"]);
|
|
353
|
+
expect(pages[1].data).toEqual(["d", "e"]);
|
|
354
|
+
expect(pages[2].data).toEqual(["f", "g"]);
|
|
355
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("should stop when next() returns null", async () => {
|
|
359
|
+
const loader = vi.fn().mockResolvedValue({
|
|
360
|
+
data: [],
|
|
361
|
+
offset: 10,
|
|
362
|
+
limit: 10,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const page = new OffsetPage({
|
|
366
|
+
params: { offset: 0, limit: 10 },
|
|
367
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
368
|
+
loader,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const pages = [];
|
|
372
|
+
for await (const p of page.pages()) {
|
|
373
|
+
pages.push(p);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
expect(pages).toHaveLength(2); // original + one fetched (which is empty/last)
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("Symbol.asyncIterator", () => {
|
|
381
|
+
it("should iterate over items in single page", async () => {
|
|
382
|
+
const page = new OffsetPage({
|
|
383
|
+
params: {},
|
|
384
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 10, total: 3 },
|
|
385
|
+
loader: vi.fn(),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const items: string[] = [];
|
|
389
|
+
for await (const item of page) {
|
|
390
|
+
items.push(item);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
expect(items).toEqual(["a", "b", "c"]);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should iterate over items across multiple pages", async () => {
|
|
397
|
+
const loader = vi.fn().mockResolvedValueOnce({
|
|
398
|
+
data: ["d", "e"],
|
|
399
|
+
offset: 3,
|
|
400
|
+
limit: 3,
|
|
401
|
+
total: 5,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const page = new OffsetPage({
|
|
405
|
+
params: { offset: 0, limit: 3 },
|
|
406
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 3, total: 5 },
|
|
407
|
+
loader,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const items: string[] = [];
|
|
411
|
+
for await (const item of page) {
|
|
412
|
+
items.push(item);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
expect(items).toEqual(["a", "b", "c", "d", "e"]);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should handle empty pages gracefully", async () => {
|
|
419
|
+
const page = new OffsetPage({
|
|
420
|
+
params: {},
|
|
421
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
422
|
+
loader: vi.fn(),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const items: string[] = [];
|
|
426
|
+
for await (const item of page) {
|
|
427
|
+
items.push(item);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
expect(items).toEqual([]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("should work with complex objects", async () => {
|
|
434
|
+
type User = { id: number; name: string };
|
|
435
|
+
const users: User[] = [
|
|
436
|
+
{ id: 1, name: "Alice" },
|
|
437
|
+
{ id: 2, name: "Bob" },
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
const page = new OffsetPage<User>({
|
|
441
|
+
params: {},
|
|
442
|
+
response: { data: users, offset: 0, limit: 10, total: 2 },
|
|
443
|
+
loader: vi.fn(),
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const collected: User[] = [];
|
|
447
|
+
for await (const user of page) {
|
|
448
|
+
collected.push(user);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
expect(collected).toEqual(users);
|
|
452
|
+
expect(collected[0]).toEqual({ id: 1, name: "Alice" });
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe("collect()", () => {
|
|
457
|
+
it("should collect all items from single page", async () => {
|
|
458
|
+
const page = new OffsetPage({
|
|
459
|
+
params: {},
|
|
460
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 10, total: 3 },
|
|
461
|
+
loader: vi.fn(),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const items = await page.collect();
|
|
465
|
+
expect(items).toEqual(["a", "b", "c"]);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("should collect all items from multiple pages", async () => {
|
|
469
|
+
const loader = vi
|
|
470
|
+
.fn()
|
|
471
|
+
.mockResolvedValueOnce({
|
|
472
|
+
data: ["d", "e", "f"],
|
|
473
|
+
offset: 3,
|
|
474
|
+
limit: 3,
|
|
475
|
+
total: 8,
|
|
476
|
+
})
|
|
477
|
+
.mockResolvedValueOnce({
|
|
478
|
+
data: ["g", "h"],
|
|
479
|
+
offset: 6,
|
|
480
|
+
limit: 3,
|
|
481
|
+
total: 8,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const page = new OffsetPage({
|
|
485
|
+
params: { offset: 0, limit: 3 },
|
|
486
|
+
response: { data: ["a", "b", "c"], offset: 0, limit: 3, total: 8 },
|
|
487
|
+
loader,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const items = await page.collect();
|
|
491
|
+
expect(items).toEqual(["a", "b", "c", "d", "e", "f", "g", "h"]);
|
|
492
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("should return empty array for empty page", async () => {
|
|
496
|
+
const page = new OffsetPage({
|
|
497
|
+
params: {},
|
|
498
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
499
|
+
loader: vi.fn(),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const items = await page.collect();
|
|
503
|
+
expect(items).toEqual([]);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("should preserve item order across pages", async () => {
|
|
507
|
+
const loader = vi
|
|
508
|
+
.fn()
|
|
509
|
+
.mockResolvedValueOnce({
|
|
510
|
+
data: [4, 5, 6],
|
|
511
|
+
offset: 3,
|
|
512
|
+
limit: 3,
|
|
513
|
+
total: 6,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const page = new OffsetPage({
|
|
517
|
+
params: { offset: 0, limit: 3 },
|
|
518
|
+
response: { data: [1, 2, 3], offset: 0, limit: 3, total: 6 },
|
|
519
|
+
loader,
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const items = await page.collect();
|
|
523
|
+
expect(items).toEqual([1, 2, 3, 4, 5, 6]);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe("edge cases", () => {
|
|
528
|
+
it("should handle consecutive next() calls correctly", async () => {
|
|
529
|
+
const loader = vi.fn().mockResolvedValue({
|
|
530
|
+
data: ["next"],
|
|
531
|
+
offset: 10,
|
|
532
|
+
limit: 10,
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const page = new OffsetPage({
|
|
536
|
+
params: { offset: 0, limit: 10 },
|
|
537
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
538
|
+
loader,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const next1 = await page.next();
|
|
542
|
+
const next2 = await page.next();
|
|
543
|
+
|
|
544
|
+
expect(next1).not.toBe(null);
|
|
545
|
+
expect(next2).not.toBe(null);
|
|
546
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("should handle loader throwing error", async () => {
|
|
550
|
+
const loader = vi.fn().mockRejectedValue(new Error("Database error"));
|
|
551
|
+
|
|
552
|
+
const page = new OffsetPage({
|
|
553
|
+
params: { offset: 0, limit: 10 },
|
|
554
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
555
|
+
loader,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await expect(page.next()).rejects.toThrow("Database error");
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("should not call loader when last is true", async () => {
|
|
562
|
+
const loader = vi.fn();
|
|
563
|
+
|
|
564
|
+
const page = new OffsetPage({
|
|
565
|
+
params: {},
|
|
566
|
+
response: { data: [], offset: 0, limit: 10 },
|
|
567
|
+
loader,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
await page.next();
|
|
571
|
+
expect(loader).not.toHaveBeenCalled();
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("should handle params with custom properties", async () => {
|
|
575
|
+
interface CustomParams extends OffsetPageParams {
|
|
576
|
+
agentId?: string;
|
|
577
|
+
filter?: string;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const loader = vi.fn().mockResolvedValue({
|
|
581
|
+
data: ["b"],
|
|
582
|
+
offset: 10,
|
|
583
|
+
limit: 10,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const page = new OffsetPage<string, CustomParams>({
|
|
587
|
+
params: { agentId: "agent-1", filter: "active", offset: 0, limit: 10 },
|
|
588
|
+
response: { data: ["a"], offset: 0, limit: 10 },
|
|
589
|
+
loader,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
await page.next();
|
|
593
|
+
|
|
594
|
+
expect(loader).toHaveBeenCalledWith({
|
|
595
|
+
agentId: "agent-1",
|
|
596
|
+
filter: "active",
|
|
597
|
+
offset: 10,
|
|
598
|
+
limit: 10,
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it("should handle large offsets correctly", async () => {
|
|
603
|
+
const loader = vi.fn().mockResolvedValue({
|
|
604
|
+
data: ["x"],
|
|
605
|
+
offset: 1000010,
|
|
606
|
+
limit: 10,
|
|
607
|
+
total: 2000000,
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const page = new OffsetPage({
|
|
611
|
+
params: { offset: 1000000, limit: 10 },
|
|
612
|
+
response: { data: ["a"], offset: 1000000, limit: 10, total: 2000000 },
|
|
613
|
+
loader,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
await page.next();
|
|
617
|
+
|
|
618
|
+
expect(loader).toHaveBeenCalledWith({
|
|
619
|
+
offset: 1000010,
|
|
620
|
+
limit: 10,
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
});
|