jazz-tools 0.15.15 → 0.16.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/.svelte-kit/__package__/jazz.class.svelte.d.ts +2 -2
- package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
- package/.svelte-kit/__package__/jazz.class.svelte.js +5 -5
- package/.svelte-kit/__package__/jazz.svelte.d.ts +2 -2
- package/.svelte-kit/__package__/jazz.svelte.d.ts.map +1 -1
- package/.turbo/turbo-build.log +46 -50
- package/CHANGELOG.md +35 -0
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser-media-images/index.d.ts +3 -1
- package/dist/browser-media-images/index.d.ts.map +1 -1
- package/dist/browser-media-images/index.js.map +1 -1
- package/dist/{chunk-4CFNXQE7.js → chunk-MLCNE3TL.js} +791 -698
- package/dist/chunk-MLCNE3TL.js.map +1 -0
- package/dist/index.js +363 -11
- package/dist/index.js.map +1 -1
- package/dist/react/hooks.d.ts +2 -2
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/testing.js +3 -1
- package/dist/react/testing.js.map +1 -1
- package/dist/react-core/hooks.d.ts +2 -2
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +3 -3
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native-core/hooks.d.ts +2 -2
- package/dist/react-native-core/hooks.d.ts.map +1 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/svelte/jazz.class.svelte.d.ts +2 -2
- package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
- package/dist/svelte/jazz.class.svelte.js +5 -5
- package/dist/svelte/jazz.svelte.d.ts +2 -2
- package/dist/svelte/jazz.svelte.d.ts.map +1 -1
- package/dist/testing.js +3 -3
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/CoValueBase.d.ts +3 -13
- package/dist/tools/coValues/CoValueBase.d.ts.map +1 -1
- package/dist/tools/coValues/account.d.ts +2 -2
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/coList.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +5 -18
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/deepLoading.d.ts +4 -1
- package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
- package/dist/tools/coValues/extensions/imageDef.d.ts +4 -7
- package/dist/tools/coValues/extensions/imageDef.d.ts.map +1 -1
- package/dist/tools/coValues/group.d.ts +1 -0
- package/dist/tools/coValues/group.d.ts.map +1 -1
- package/dist/tools/coValues/inbox.d.ts +2 -2
- package/dist/tools/coValues/inbox.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +58 -17
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/coValues/request.d.ts +82 -0
- package/dist/tools/coValues/request.d.ts.map +1 -0
- package/dist/tools/coValues/schemaUnion.d.ts +5 -1
- package/dist/tools/coValues/schemaUnion.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +4 -3
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/createContext.d.ts +4 -4
- package/dist/tools/implementation/createContext.d.ts.map +1 -1
- package/dist/tools/implementation/invites.d.ts +2 -2
- package/dist/tools/implementation/invites.d.ts.map +1 -1
- package/dist/tools/implementation/schemaUtils.d.ts +8 -0
- package/dist/tools/implementation/schemaUtils.d.ts.map +1 -0
- package/dist/tools/implementation/zodSchema/coExport.d.ts +11 -1
- package/dist/tools/implementation/zodSchema/coExport.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts +22 -0
- package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts.map +1 -0
- package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts +10 -0
- package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +11 -11
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts +34 -24
- package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +16 -14
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +23 -17
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +24 -16
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoOptionalSchema.d.ts +20 -9
- package/dist/tools/implementation/zodSchema/schemaTypes/CoOptionalSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +18 -12
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoValueSchema.d.ts +18 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/CoValueSchema.d.ts.map +1 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +14 -9
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +14 -9
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +14 -9
- package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts +15 -13
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts +12 -15
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts +17 -20
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts +17 -20
- package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/unionUtils.d.ts +3 -5
- package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts +10 -8
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodReExport.d.ts +4 -8
- package/dist/tools/implementation/zodSchema/zodReExport.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodSchema.d.ts +21 -30
- package/dist/tools/implementation/zodSchema/zodSchema.d.ts.map +1 -1
- package/dist/tools/internal.d.ts +3 -2
- package/dist/tools/internal.d.ts.map +1 -1
- package/dist/tools/lib/id.d.ts +2 -0
- package/dist/tools/lib/id.d.ts.map +1 -0
- package/dist/tools/lib/utilityTypes.d.ts +10 -0
- package/dist/tools/lib/utilityTypes.d.ts.map +1 -0
- package/dist/tools/subscribe/SubscriptionScope.d.ts +3 -2
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/utils.d.ts.map +1 -1
- package/dist/tools/testing.d.ts +2 -2
- package/dist/tools/testing.d.ts.map +1 -1
- package/dist/tools/tests/exportImport.test.d.ts +2 -0
- package/dist/tools/tests/exportImport.test.d.ts.map +1 -0
- package/dist/tools/tests/request.test.d.ts +2 -0
- package/dist/tools/tests/request.test.d.ts.map +1 -0
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +2 -2
- package/dist/worker/index.js.map +1 -1
- package/package.json +7 -6
- package/src/browser/index.ts +2 -4
- package/src/browser-media-images/index.ts +1 -1
- package/src/react/hooks.tsx +2 -2
- package/src/react-core/hooks.ts +6 -6
- package/src/react-core/tests/useAccount.test.ts +2 -2
- package/src/react-core/tests/useCoState.test.ts +3 -2
- package/src/react-native-core/hooks.tsx +2 -2
- package/src/svelte/jazz.class.svelte.ts +10 -7
- package/src/svelte/jazz.svelte.ts +2 -2
- package/src/tools/coValues/CoValueBase.ts +8 -20
- package/src/tools/coValues/account.ts +18 -14
- package/src/tools/coValues/coFeed.ts +0 -4
- package/src/tools/coValues/coList.ts +7 -9
- package/src/tools/coValues/coMap.ts +1 -6
- package/src/tools/coValues/coPlainText.ts +4 -4
- package/src/tools/coValues/deepLoading.ts +4 -1
- package/src/tools/coValues/extensions/imageDef.ts +3 -3
- package/src/tools/coValues/group.ts +1 -0
- package/src/tools/coValues/inbox.ts +8 -7
- package/src/tools/coValues/interfaces.ts +177 -69
- package/src/tools/coValues/request.ts +633 -0
- package/src/tools/coValues/schemaUnion.ts +8 -4
- package/src/tools/exports.ts +11 -14
- package/src/tools/implementation/createContext.ts +9 -9
- package/src/tools/implementation/invites.ts +2 -2
- package/src/tools/implementation/schemaUtils.ts +18 -0
- package/src/tools/implementation/zodSchema/coExport.ts +14 -0
- package/src/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.ts +156 -0
- package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +133 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +36 -17
- package/src/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.ts +101 -52
- package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +54 -50
- package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +54 -46
- package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +187 -137
- package/src/tools/implementation/zodSchema/schemaTypes/CoOptionalSchema.ts +29 -27
- package/src/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.ts +28 -18
- package/src/tools/implementation/zodSchema/schemaTypes/CoValueSchema.ts +18 -0
- package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +45 -36
- package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +47 -35
- package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +43 -30
- package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.ts +28 -23
- package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.ts +28 -25
- package/src/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.ts +86 -78
- package/src/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.ts +91 -82
- package/src/tools/implementation/zodSchema/unionUtils.ts +47 -51
- package/src/tools/implementation/zodSchema/zodCo.ts +42 -75
- package/src/tools/implementation/zodSchema/zodReExport.ts +44 -24
- package/src/tools/implementation/zodSchema/zodSchema.ts +64 -102
- package/src/tools/internal.ts +3 -2
- package/src/tools/lib/id.ts +3 -0
- package/src/tools/lib/utilityTypes.ts +7 -0
- package/src/tools/subscribe/SubscriptionScope.ts +10 -1
- package/src/tools/subscribe/utils.ts +4 -2
- package/src/tools/testing.ts +4 -4
- package/src/tools/tests/ContextManager.test.ts +8 -9
- package/src/tools/tests/account.test.ts +65 -3
- package/src/tools/tests/coDiscriminatedUnion.test-d.ts +38 -0
- package/src/tools/tests/coDiscriminatedUnion.test.ts +219 -1
- package/src/tools/tests/coFeed.test-d.ts +4 -3
- package/src/tools/tests/coList.test-d.ts +32 -3
- package/src/tools/tests/coList.test.ts +20 -2
- package/src/tools/tests/coMap.record.test-d.ts +31 -3
- package/src/tools/tests/coMap.record.test.ts +9 -9
- package/src/tools/tests/coMap.test-d.ts +8 -8
- package/src/tools/tests/coMap.test.ts +19 -5
- package/src/tools/tests/coOptional.test.ts +63 -1
- package/src/tools/tests/createContext.test.ts +7 -9
- package/src/tools/tests/deepLoading.test.ts +4 -10
- package/src/tools/tests/exportImport.test.ts +526 -0
- package/src/tools/tests/groupsAndAccounts.test.ts +5 -4
- package/src/tools/tests/inbox.test.ts +3 -2
- package/src/tools/tests/load.test.ts +3 -29
- package/src/tools/tests/request.test.ts +951 -0
- package/src/tools/tests/schemaUnion.test.ts +2 -2
- package/src/tools/tests/subscribe.test.ts +22 -114
- package/src/tools/tests/testing.test.ts +6 -6
- package/src/tools/tests/zod.test-d.ts +27 -0
- package/src/tools/tests/zod.test.ts +50 -45
- package/src/worker/index.ts +0 -1
- package/tsup.config.ts +0 -2
- package/dist/chunk-4CFNXQE7.js.map +0 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.d.ts +0 -12
- package/dist/tools/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.d.ts.map +0 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/zodSchemaToCoSchema.d.ts +0 -9
- package/dist/tools/implementation/zodSchema/runtimeConverters/zodSchemaToCoSchema.d.ts.map +0 -1
- package/src/tools/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.ts +0 -172
- package/src/tools/implementation/zodSchema/runtimeConverters/zodSchemaToCoSchema.ts +0 -218
@@ -0,0 +1,951 @@
|
|
1
|
+
import { http } from "msw";
|
2
|
+
import { setupServer } from "msw/node";
|
3
|
+
import { assert, describe, expect, it, vi } from "vitest";
|
4
|
+
import { afterAll, afterEach, beforeAll } from "vitest";
|
5
|
+
import { z } from "zod/v4";
|
6
|
+
import {
|
7
|
+
JazzRequestError,
|
8
|
+
experimental_defineRequest,
|
9
|
+
isJazzRequestError,
|
10
|
+
} from "../coValues/request.js";
|
11
|
+
import { Account, CoPlainText, Group, co } from "../index.js";
|
12
|
+
import { exportCoValue, importContentPieces } from "../internal.js";
|
13
|
+
import { createJazzTestAccount, linkAccounts } from "../testing.js";
|
14
|
+
|
15
|
+
const server = setupServer();
|
16
|
+
|
17
|
+
beforeAll(() => server.listen());
|
18
|
+
afterEach(() => server.resetHandlers());
|
19
|
+
afterEach(() => vi.restoreAllMocks());
|
20
|
+
afterAll(() => server.close());
|
21
|
+
|
22
|
+
async function setupAccounts() {
|
23
|
+
const me = await createJazzTestAccount();
|
24
|
+
const worker = await createJazzTestAccount();
|
25
|
+
|
26
|
+
const workerPieces = await exportCoValue(Account, worker.id, {
|
27
|
+
loadAs: worker,
|
28
|
+
});
|
29
|
+
|
30
|
+
importContentPieces(workerPieces ?? [], me);
|
31
|
+
|
32
|
+
return { me, worker };
|
33
|
+
}
|
34
|
+
|
35
|
+
describe("experimental_defineRequest", () => {
|
36
|
+
describe("full request/response cycle", () => {
|
37
|
+
it("should accept the CoMap init as the request payload and as response callback return value", async () => {
|
38
|
+
const { me, worker } = await setupAccounts();
|
39
|
+
|
40
|
+
const group = Group.create(me);
|
41
|
+
group.addMember("everyone", "writer");
|
42
|
+
|
43
|
+
const userRequest = experimental_defineRequest({
|
44
|
+
url: "https://api.example.com/api/user",
|
45
|
+
workerId: worker.id,
|
46
|
+
request: {
|
47
|
+
name: z.string(),
|
48
|
+
email: z.string(),
|
49
|
+
age: z.number(),
|
50
|
+
},
|
51
|
+
response: {
|
52
|
+
bio: z.string(),
|
53
|
+
avatar: z.string().optional(),
|
54
|
+
},
|
55
|
+
});
|
56
|
+
|
57
|
+
let receivedUser: unknown;
|
58
|
+
let receivedMadeBy: unknown;
|
59
|
+
let requestOwner: Account | Group;
|
60
|
+
|
61
|
+
server.use(
|
62
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
63
|
+
try {
|
64
|
+
return await userRequest.handle(
|
65
|
+
request,
|
66
|
+
worker,
|
67
|
+
async (user, madeBy) => {
|
68
|
+
receivedUser = user.toJSON();
|
69
|
+
requestOwner = user._owner;
|
70
|
+
receivedMadeBy = madeBy.id;
|
71
|
+
|
72
|
+
// Return a plain object (CoMapInit) instead of a CoMap instance
|
73
|
+
return {
|
74
|
+
bio: `Profile for ${user.name}`,
|
75
|
+
avatar: `https://example.com/avatars/${user.email}.jpg`,
|
76
|
+
};
|
77
|
+
},
|
78
|
+
);
|
79
|
+
} catch (error) {
|
80
|
+
console.error(error);
|
81
|
+
throw error;
|
82
|
+
}
|
83
|
+
}),
|
84
|
+
);
|
85
|
+
|
86
|
+
// Send a plain object (CoMapInit) instead of a CoMap instance
|
87
|
+
const response = await userRequest.send(
|
88
|
+
{
|
89
|
+
name: "John Doe",
|
90
|
+
email: "john@example.com",
|
91
|
+
age: 30,
|
92
|
+
},
|
93
|
+
{ owner: me },
|
94
|
+
);
|
95
|
+
|
96
|
+
// Verify the response is a proper CoMap instance
|
97
|
+
expect(response.bio).toEqual("Profile for John Doe");
|
98
|
+
expect(response.avatar).toEqual(
|
99
|
+
"https://example.com/avatars/john@example.com.jpg",
|
100
|
+
);
|
101
|
+
|
102
|
+
expect(requestOwner!.members.map((m) => [m.account.id, m.role])).toEqual([
|
103
|
+
[me.id, "admin"],
|
104
|
+
[worker.id, "writer"],
|
105
|
+
]);
|
106
|
+
|
107
|
+
expect(
|
108
|
+
response._owner.members.map((m) => [m.account.id, m.role]),
|
109
|
+
).toEqual([
|
110
|
+
[worker.id, "admin"],
|
111
|
+
[me.id, "reader"],
|
112
|
+
]);
|
113
|
+
|
114
|
+
// Verify the server received the correct data
|
115
|
+
expect(receivedUser).toMatchObject({
|
116
|
+
_type: "CoMap",
|
117
|
+
name: "John Doe",
|
118
|
+
email: "john@example.com",
|
119
|
+
age: 30,
|
120
|
+
});
|
121
|
+
expect(receivedMadeBy).toEqual(me.id);
|
122
|
+
});
|
123
|
+
|
124
|
+
it("should push the response content directly to the client", async () => {
|
125
|
+
const { me, worker } = await setupAccounts();
|
126
|
+
|
127
|
+
const group = Group.create(me);
|
128
|
+
group.addMember("everyone", "writer");
|
129
|
+
|
130
|
+
const Address = co.map({
|
131
|
+
street: co.plainText(),
|
132
|
+
city: co.plainText(),
|
133
|
+
});
|
134
|
+
|
135
|
+
const Person = co.map({
|
136
|
+
name: z.string(),
|
137
|
+
address: Address,
|
138
|
+
});
|
139
|
+
|
140
|
+
const userRequest = experimental_defineRequest({
|
141
|
+
url: "https://api.example.com/api/user",
|
142
|
+
workerId: worker.id,
|
143
|
+
request: {
|
144
|
+
name: z.string(),
|
145
|
+
email: z.string(),
|
146
|
+
age: z.number(),
|
147
|
+
},
|
148
|
+
response: {
|
149
|
+
schema: {
|
150
|
+
person: Person,
|
151
|
+
},
|
152
|
+
resolve: {
|
153
|
+
person: {
|
154
|
+
address: {
|
155
|
+
street: true,
|
156
|
+
city: true,
|
157
|
+
},
|
158
|
+
},
|
159
|
+
},
|
160
|
+
},
|
161
|
+
});
|
162
|
+
|
163
|
+
server.use(
|
164
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
165
|
+
try {
|
166
|
+
return await userRequest.handle(
|
167
|
+
request,
|
168
|
+
worker,
|
169
|
+
async (user, madeBy) => {
|
170
|
+
const group = Group.create(me);
|
171
|
+
group.addMember(madeBy, "writer");
|
172
|
+
|
173
|
+
const person = Person.create(
|
174
|
+
{
|
175
|
+
name: user.name,
|
176
|
+
address: Address.create(
|
177
|
+
{
|
178
|
+
street: CoPlainText.create("123 Main St", group),
|
179
|
+
city: CoPlainText.create("New York", group),
|
180
|
+
},
|
181
|
+
group,
|
182
|
+
),
|
183
|
+
},
|
184
|
+
group,
|
185
|
+
);
|
186
|
+
|
187
|
+
return {
|
188
|
+
person,
|
189
|
+
};
|
190
|
+
},
|
191
|
+
);
|
192
|
+
} catch (error) {
|
193
|
+
console.error(error);
|
194
|
+
throw error;
|
195
|
+
}
|
196
|
+
}),
|
197
|
+
);
|
198
|
+
|
199
|
+
// Send a plain object (CoMapInit) instead of a CoMap instance
|
200
|
+
const response = await userRequest.send(
|
201
|
+
{
|
202
|
+
name: "John Doe",
|
203
|
+
email: "john@example.com",
|
204
|
+
age: 30,
|
205
|
+
},
|
206
|
+
{ owner: me },
|
207
|
+
);
|
208
|
+
|
209
|
+
// Verify the response is a proper CoMap instance
|
210
|
+
expect(response.person.name).toEqual("John Doe");
|
211
|
+
expect(response.person.address.street.toString()).toEqual("123 Main St");
|
212
|
+
expect(response.person.address.city.toString()).toEqual("New York");
|
213
|
+
});
|
214
|
+
});
|
215
|
+
|
216
|
+
it("should handle errors on child covalues gracefully", async () => {
|
217
|
+
const { me, worker } = await setupAccounts();
|
218
|
+
|
219
|
+
await linkAccounts(me, worker);
|
220
|
+
|
221
|
+
const Address = co.map({
|
222
|
+
street: co.plainText(),
|
223
|
+
city: co.plainText(),
|
224
|
+
});
|
225
|
+
|
226
|
+
const Person = co.map({
|
227
|
+
name: z.string(),
|
228
|
+
address: Address,
|
229
|
+
});
|
230
|
+
|
231
|
+
const privateToWorker = Group.create(worker);
|
232
|
+
const privateToMe = Group.create(me);
|
233
|
+
const publicGroup = Group.create(me).makePublic();
|
234
|
+
const address = Address.create(
|
235
|
+
{
|
236
|
+
street: CoPlainText.create("123 Main St", privateToWorker),
|
237
|
+
city: CoPlainText.create("New York", privateToMe),
|
238
|
+
},
|
239
|
+
publicGroup,
|
240
|
+
);
|
241
|
+
const person = Person.create(
|
242
|
+
{
|
243
|
+
name: "John",
|
244
|
+
address,
|
245
|
+
},
|
246
|
+
publicGroup,
|
247
|
+
);
|
248
|
+
|
249
|
+
const personRequest = experimental_defineRequest({
|
250
|
+
url: "https://api.example.com/api/person",
|
251
|
+
workerId: worker.id,
|
252
|
+
request: {
|
253
|
+
schema: {
|
254
|
+
person: Person,
|
255
|
+
},
|
256
|
+
resolve: { person: { address: { street: true } } },
|
257
|
+
},
|
258
|
+
response: {
|
259
|
+
schema: {
|
260
|
+
person: Person,
|
261
|
+
},
|
262
|
+
resolve: { person: { address: { street: true, city: true } } },
|
263
|
+
},
|
264
|
+
});
|
265
|
+
|
266
|
+
server.use(
|
267
|
+
http.post("https://api.example.com/api/person", async ({ request }) => {
|
268
|
+
return personRequest.handle(
|
269
|
+
request,
|
270
|
+
worker,
|
271
|
+
async ({ person }, madeBy) => {
|
272
|
+
person.address.street._owner
|
273
|
+
.castAs(Group)
|
274
|
+
.addMember(madeBy, "reader");
|
275
|
+
|
276
|
+
// The request should handle the error gracefully when trying to resolve
|
277
|
+
// child covalues that the worker doesn't have access to
|
278
|
+
return { person };
|
279
|
+
},
|
280
|
+
);
|
281
|
+
}),
|
282
|
+
);
|
283
|
+
|
284
|
+
// Send the request - this should not throw even though the worker
|
285
|
+
// doesn't have access to the address's child covalues
|
286
|
+
const response = await personRequest.send({ person }, { owner: me });
|
287
|
+
|
288
|
+
// Verify the response is still a proper Person instance
|
289
|
+
expect(response.person.name).toEqual("John");
|
290
|
+
expect(response.person.address.street.toString()).toBe("123 Main St");
|
291
|
+
expect(response.person.address.city.toString()).toBe("New York");
|
292
|
+
});
|
293
|
+
|
294
|
+
it("should accept void responses", async () => {
|
295
|
+
const { me, worker } = await setupAccounts();
|
296
|
+
|
297
|
+
const group = Group.create(me);
|
298
|
+
group.addMember("everyone", "writer");
|
299
|
+
|
300
|
+
const userRequest = experimental_defineRequest({
|
301
|
+
url: "https://api.example.com/api/user",
|
302
|
+
workerId: worker.id,
|
303
|
+
request: {
|
304
|
+
name: z.string(),
|
305
|
+
email: z.string(),
|
306
|
+
age: z.number(),
|
307
|
+
},
|
308
|
+
response: {},
|
309
|
+
});
|
310
|
+
|
311
|
+
let receivedUser: unknown;
|
312
|
+
let receivedMadeBy: unknown;
|
313
|
+
|
314
|
+
server.use(
|
315
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
316
|
+
try {
|
317
|
+
return await userRequest.handle(
|
318
|
+
request,
|
319
|
+
worker,
|
320
|
+
async (user, madeBy) => {
|
321
|
+
receivedUser = user.toJSON();
|
322
|
+
receivedMadeBy = madeBy.id;
|
323
|
+
},
|
324
|
+
);
|
325
|
+
} catch (error) {
|
326
|
+
console.error(error);
|
327
|
+
throw error;
|
328
|
+
}
|
329
|
+
}),
|
330
|
+
);
|
331
|
+
|
332
|
+
// Send a plain object (CoMapInit) instead of a CoMap instance
|
333
|
+
await userRequest.send(
|
334
|
+
{
|
335
|
+
name: "John Doe",
|
336
|
+
email: "john@example.com",
|
337
|
+
age: 30,
|
338
|
+
},
|
339
|
+
{ owner: me },
|
340
|
+
);
|
341
|
+
|
342
|
+
// Verify the server received the correct data
|
343
|
+
expect(receivedUser).toMatchObject({
|
344
|
+
_type: "CoMap",
|
345
|
+
name: "John Doe",
|
346
|
+
email: "john@example.com",
|
347
|
+
age: 30,
|
348
|
+
});
|
349
|
+
expect(receivedMadeBy).toEqual(me.id);
|
350
|
+
});
|
351
|
+
|
352
|
+
it("should accept group as workerId", async () => {
|
353
|
+
const { me, worker } = await setupAccounts();
|
354
|
+
|
355
|
+
await linkAccounts(me, worker);
|
356
|
+
|
357
|
+
// Create a group that will act as the worker
|
358
|
+
const workerGroup = Group.create(worker);
|
359
|
+
|
360
|
+
const userRequest = experimental_defineRequest({
|
361
|
+
url: "https://api.example.com/api/user",
|
362
|
+
workerId: workerGroup.id, // Use group ID instead of account ID
|
363
|
+
request: {
|
364
|
+
name: z.string(),
|
365
|
+
email: z.string(),
|
366
|
+
age: z.number(),
|
367
|
+
},
|
368
|
+
response: {
|
369
|
+
bio: z.string(),
|
370
|
+
avatar: z.string().optional(),
|
371
|
+
},
|
372
|
+
});
|
373
|
+
|
374
|
+
let receivedUser: unknown;
|
375
|
+
let receivedMadeBy: unknown;
|
376
|
+
|
377
|
+
server.use(
|
378
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
379
|
+
try {
|
380
|
+
return await userRequest.handle(
|
381
|
+
request,
|
382
|
+
worker, // The worker account handles the request
|
383
|
+
async (user, madeBy) => {
|
384
|
+
receivedUser = user.toJSON();
|
385
|
+
receivedMadeBy = madeBy.id;
|
386
|
+
|
387
|
+
return {
|
388
|
+
bio: `Profile for ${user.name}`,
|
389
|
+
avatar: `https://example.com/avatars/${user.email}.jpg`,
|
390
|
+
};
|
391
|
+
},
|
392
|
+
);
|
393
|
+
} catch (error) {
|
394
|
+
console.error(error);
|
395
|
+
throw error;
|
396
|
+
}
|
397
|
+
}),
|
398
|
+
);
|
399
|
+
|
400
|
+
// Send a request - this should work with group as workerId
|
401
|
+
const response = await userRequest.send(
|
402
|
+
{
|
403
|
+
name: "John Doe",
|
404
|
+
email: "john@example.com",
|
405
|
+
age: 30,
|
406
|
+
},
|
407
|
+
{ owner: me },
|
408
|
+
);
|
409
|
+
|
410
|
+
// Verify the response is a proper CoMap instance
|
411
|
+
expect(response.bio).toEqual("Profile for John Doe");
|
412
|
+
expect(response.avatar).toEqual(
|
413
|
+
"https://example.com/avatars/john@example.com.jpg",
|
414
|
+
);
|
415
|
+
|
416
|
+
// Verify the response owner structure - should include the worker account
|
417
|
+
expect(response._owner.members.map((m) => [m.account.id, m.role])).toEqual([
|
418
|
+
[worker.id, "admin"],
|
419
|
+
[me.id, "reader"],
|
420
|
+
]);
|
421
|
+
|
422
|
+
// Verify the server received the correct data
|
423
|
+
expect(receivedUser).toMatchObject({
|
424
|
+
_type: "CoMap",
|
425
|
+
name: "John Doe",
|
426
|
+
email: "john@example.com",
|
427
|
+
age: 30,
|
428
|
+
});
|
429
|
+
expect(receivedMadeBy).toEqual(me.id);
|
430
|
+
});
|
431
|
+
});
|
432
|
+
|
433
|
+
describe("JazzRequestError handling", () => {
|
434
|
+
describe("System-defined errors in request.ts", () => {
|
435
|
+
it("should throw error when request payload is invalid", async () => {
|
436
|
+
const { me, worker } = await setupAccounts();
|
437
|
+
|
438
|
+
const userRequest = experimental_defineRequest({
|
439
|
+
url: "https://api.example.com/api/user",
|
440
|
+
workerId: worker.id,
|
441
|
+
request: {
|
442
|
+
name: z.string(),
|
443
|
+
email: z.string(),
|
444
|
+
},
|
445
|
+
response: {
|
446
|
+
bio: z.string(),
|
447
|
+
},
|
448
|
+
});
|
449
|
+
|
450
|
+
server.use(
|
451
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
452
|
+
return userRequest.handle(request, worker, async (user, madeBy) => {
|
453
|
+
return { bio: "test" };
|
454
|
+
});
|
455
|
+
}),
|
456
|
+
);
|
457
|
+
|
458
|
+
// Mock fetch to return invalid JSON
|
459
|
+
const originalFetch = global.fetch;
|
460
|
+
global.fetch = vi.fn().mockResolvedValue({
|
461
|
+
ok: true,
|
462
|
+
json: () => Promise.resolve({ invalid: "payload" }),
|
463
|
+
});
|
464
|
+
|
465
|
+
await expect(
|
466
|
+
userRequest.send(
|
467
|
+
{
|
468
|
+
name: "John Doe",
|
469
|
+
email: "john@example.com",
|
470
|
+
},
|
471
|
+
{ owner: me },
|
472
|
+
),
|
473
|
+
).rejects.toMatchInlineSnapshot(`
|
474
|
+
{
|
475
|
+
"code": 400,
|
476
|
+
"details": [ZodError: [
|
477
|
+
{
|
478
|
+
"code": "invalid_value",
|
479
|
+
"values": [
|
480
|
+
"success"
|
481
|
+
],
|
482
|
+
"path": [
|
483
|
+
"type"
|
484
|
+
],
|
485
|
+
"message": "Invalid input: expected \\"success\\""
|
486
|
+
}
|
487
|
+
]],
|
488
|
+
"message": "Response payload is not valid",
|
489
|
+
}
|
490
|
+
`);
|
491
|
+
|
492
|
+
global.fetch = originalFetch;
|
493
|
+
});
|
494
|
+
|
495
|
+
it("should throw error when request payload is already handled", async () => {
|
496
|
+
const { me, worker } = await setupAccounts();
|
497
|
+
|
498
|
+
const userRequest = experimental_defineRequest({
|
499
|
+
url: "https://api.example.com/api/user",
|
500
|
+
workerId: worker.id,
|
501
|
+
request: {
|
502
|
+
name: z.string(),
|
503
|
+
email: z.string(),
|
504
|
+
},
|
505
|
+
response: {
|
506
|
+
bio: z.string(),
|
507
|
+
},
|
508
|
+
});
|
509
|
+
|
510
|
+
server.use(
|
511
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
512
|
+
// Mock to make it possible to call json() twice
|
513
|
+
const body = await request.json();
|
514
|
+
vi.spyOn(request, "json").mockResolvedValue(body);
|
515
|
+
|
516
|
+
// First call should succeed
|
517
|
+
await userRequest.handle(request, worker, async () => {
|
518
|
+
return { bio: "test" };
|
519
|
+
});
|
520
|
+
|
521
|
+
// Second call with same ID should fail
|
522
|
+
return userRequest.handle(request, worker, async () => {
|
523
|
+
return { bio: "test" };
|
524
|
+
});
|
525
|
+
}),
|
526
|
+
);
|
527
|
+
|
528
|
+
await expect(
|
529
|
+
userRequest.send(
|
530
|
+
{
|
531
|
+
name: "John Doe",
|
532
|
+
email: "john@example.com",
|
533
|
+
},
|
534
|
+
{ owner: me },
|
535
|
+
),
|
536
|
+
).rejects.toMatchInlineSnapshot(`
|
537
|
+
{
|
538
|
+
"code": 400,
|
539
|
+
"details": undefined,
|
540
|
+
"message": "Request payload is already handled",
|
541
|
+
}
|
542
|
+
`);
|
543
|
+
});
|
544
|
+
|
545
|
+
it("should throw error when authentication token is expired", async () => {
|
546
|
+
const { me, worker } = await setupAccounts();
|
547
|
+
|
548
|
+
const userRequest = experimental_defineRequest({
|
549
|
+
url: "https://api.example.com/api/user",
|
550
|
+
workerId: worker.id,
|
551
|
+
request: {
|
552
|
+
name: z.string(),
|
553
|
+
email: z.string(),
|
554
|
+
},
|
555
|
+
response: {
|
556
|
+
bio: z.string(),
|
557
|
+
},
|
558
|
+
});
|
559
|
+
|
560
|
+
server.use(
|
561
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
562
|
+
const body = await request.json();
|
563
|
+
|
564
|
+
assert(typeof body === "object");
|
565
|
+
assert(body);
|
566
|
+
|
567
|
+
body.createdAt = Date.now() - 1000 * 70; // 70 seconds ago
|
568
|
+
vi.spyOn(request, "json").mockResolvedValue(body);
|
569
|
+
|
570
|
+
return userRequest.handle(request, worker, async () => {
|
571
|
+
return { bio: "test" };
|
572
|
+
});
|
573
|
+
}),
|
574
|
+
);
|
575
|
+
|
576
|
+
await expect(
|
577
|
+
userRequest.send(
|
578
|
+
{
|
579
|
+
name: "John Doe",
|
580
|
+
email: "john@example.com",
|
581
|
+
},
|
582
|
+
{ owner: me },
|
583
|
+
),
|
584
|
+
).rejects.toMatchInlineSnapshot(`
|
585
|
+
{
|
586
|
+
"code": 401,
|
587
|
+
"details": undefined,
|
588
|
+
"message": "Authentication token is expired",
|
589
|
+
}
|
590
|
+
`);
|
591
|
+
});
|
592
|
+
|
593
|
+
it("should throw error when signature is invalid", async () => {
|
594
|
+
const { me, worker } = await setupAccounts();
|
595
|
+
|
596
|
+
const userRequest = experimental_defineRequest({
|
597
|
+
url: "https://api.example.com/api/user",
|
598
|
+
workerId: worker.id,
|
599
|
+
request: {
|
600
|
+
name: z.string(),
|
601
|
+
email: z.string(),
|
602
|
+
},
|
603
|
+
response: {
|
604
|
+
bio: z.string(),
|
605
|
+
},
|
606
|
+
});
|
607
|
+
|
608
|
+
server.use(
|
609
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
610
|
+
const body = await request.json();
|
611
|
+
|
612
|
+
assert(typeof body === "object");
|
613
|
+
assert(body);
|
614
|
+
|
615
|
+
body.authToken = "signature_zinvalid";
|
616
|
+
vi.spyOn(request, "json").mockResolvedValue(body);
|
617
|
+
|
618
|
+
return userRequest.handle(request, worker, async () => {
|
619
|
+
return { bio: "test" };
|
620
|
+
});
|
621
|
+
}),
|
622
|
+
);
|
623
|
+
|
624
|
+
await expect(
|
625
|
+
userRequest.send(
|
626
|
+
{
|
627
|
+
name: "John Doe",
|
628
|
+
email: "john@example.com",
|
629
|
+
},
|
630
|
+
{ owner: me },
|
631
|
+
),
|
632
|
+
).rejects.toMatchInlineSnapshot(`
|
633
|
+
{
|
634
|
+
"code": 401,
|
635
|
+
"details": undefined,
|
636
|
+
"message": "Invalid signature",
|
637
|
+
}
|
638
|
+
`);
|
639
|
+
});
|
640
|
+
|
641
|
+
it("should throw error when creator account not found", async () => {
|
642
|
+
const { me, worker } = await setupAccounts();
|
643
|
+
|
644
|
+
const userRequest = experimental_defineRequest({
|
645
|
+
url: "https://api.example.com/api/user",
|
646
|
+
workerId: worker.id,
|
647
|
+
request: {
|
648
|
+
name: z.string(),
|
649
|
+
email: z.string(),
|
650
|
+
},
|
651
|
+
response: {
|
652
|
+
bio: z.string(),
|
653
|
+
},
|
654
|
+
});
|
655
|
+
|
656
|
+
server.use(
|
657
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
658
|
+
vi.spyOn(Account, "load").mockResolvedValue(null);
|
659
|
+
|
660
|
+
return userRequest.handle(request, worker, async () => {
|
661
|
+
return { bio: "test" };
|
662
|
+
});
|
663
|
+
}),
|
664
|
+
);
|
665
|
+
|
666
|
+
await expect(
|
667
|
+
userRequest.send(
|
668
|
+
{
|
669
|
+
name: "John Doe",
|
670
|
+
email: "john@example.com",
|
671
|
+
},
|
672
|
+
{ owner: me },
|
673
|
+
),
|
674
|
+
).rejects.toMatchInlineSnapshot(`
|
675
|
+
{
|
676
|
+
"code": 400,
|
677
|
+
"details": undefined,
|
678
|
+
"message": "Creator account not found",
|
679
|
+
}
|
680
|
+
`);
|
681
|
+
|
682
|
+
vi.restoreAllMocks();
|
683
|
+
});
|
684
|
+
|
685
|
+
it("should throw error when there are not enough permissions to resolve the request payload", async () => {
|
686
|
+
const { me, worker } = await setupAccounts();
|
687
|
+
|
688
|
+
// Link the accounts to ensure that the request payload is loaded
|
689
|
+
await linkAccounts(me, worker);
|
690
|
+
|
691
|
+
const User = co.map({
|
692
|
+
name: z.string(),
|
693
|
+
email: z.string(),
|
694
|
+
});
|
695
|
+
|
696
|
+
const userRequest = experimental_defineRequest({
|
697
|
+
url: "https://api.example.com/api/user",
|
698
|
+
workerId: worker.id,
|
699
|
+
request: {
|
700
|
+
schema: {
|
701
|
+
user: User,
|
702
|
+
},
|
703
|
+
resolve: {
|
704
|
+
user: true,
|
705
|
+
},
|
706
|
+
},
|
707
|
+
response: {
|
708
|
+
bio: z.string(),
|
709
|
+
},
|
710
|
+
});
|
711
|
+
|
712
|
+
server.use(
|
713
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
714
|
+
return userRequest.handle(request, worker, async (user, madeBy) => {
|
715
|
+
return { bio: "test" };
|
716
|
+
});
|
717
|
+
}),
|
718
|
+
);
|
719
|
+
|
720
|
+
await expect(
|
721
|
+
userRequest.send(
|
722
|
+
{
|
723
|
+
user: User.create(
|
724
|
+
{
|
725
|
+
name: "John Doe",
|
726
|
+
email: "john@example.com",
|
727
|
+
},
|
728
|
+
me,
|
729
|
+
),
|
730
|
+
},
|
731
|
+
{ owner: me },
|
732
|
+
),
|
733
|
+
).rejects.toMatchInlineSnapshot(`
|
734
|
+
{
|
735
|
+
"code": 400,
|
736
|
+
"details": undefined,
|
737
|
+
"message": "Value not found",
|
738
|
+
}
|
739
|
+
`);
|
740
|
+
|
741
|
+
vi.restoreAllMocks();
|
742
|
+
});
|
743
|
+
|
744
|
+
it("should throw error when the request payload is not found", async () => {
|
745
|
+
const { me, worker } = await setupAccounts();
|
746
|
+
|
747
|
+
const User = co.map({
|
748
|
+
name: z.string(),
|
749
|
+
email: z.string(),
|
750
|
+
});
|
751
|
+
|
752
|
+
const userRequest = experimental_defineRequest({
|
753
|
+
url: "https://api.example.com/api/user",
|
754
|
+
workerId: worker.id,
|
755
|
+
request: {
|
756
|
+
schema: {
|
757
|
+
user: User,
|
758
|
+
},
|
759
|
+
resolve: {
|
760
|
+
user: true,
|
761
|
+
},
|
762
|
+
},
|
763
|
+
response: {
|
764
|
+
bio: z.string(),
|
765
|
+
},
|
766
|
+
});
|
767
|
+
|
768
|
+
server.use(
|
769
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
770
|
+
return userRequest.handle(request, worker, async (user, madeBy) => {
|
771
|
+
return { bio: "test" };
|
772
|
+
});
|
773
|
+
}),
|
774
|
+
);
|
775
|
+
|
776
|
+
const group = Group.create(me);
|
777
|
+
group.makePublic();
|
778
|
+
|
779
|
+
const user = User.create(
|
780
|
+
{
|
781
|
+
name: "John Doe",
|
782
|
+
email: "john@example.com",
|
783
|
+
},
|
784
|
+
group,
|
785
|
+
);
|
786
|
+
|
787
|
+
await expect(
|
788
|
+
userRequest.send(
|
789
|
+
{
|
790
|
+
user,
|
791
|
+
},
|
792
|
+
{ owner: me },
|
793
|
+
),
|
794
|
+
).rejects.toMatchInlineSnapshot(`
|
795
|
+
{
|
796
|
+
"code": 400,
|
797
|
+
"details": undefined,
|
798
|
+
"message": "Value not found",
|
799
|
+
}
|
800
|
+
`);
|
801
|
+
|
802
|
+
vi.restoreAllMocks();
|
803
|
+
});
|
804
|
+
|
805
|
+
it("should throw error when the server returns a non-200 status code", async () => {
|
806
|
+
const { me, worker } = await setupAccounts();
|
807
|
+
|
808
|
+
const userRequest = experimental_defineRequest({
|
809
|
+
url: "https://api.example.com/api/user",
|
810
|
+
workerId: worker.id,
|
811
|
+
request: {
|
812
|
+
name: z.string(),
|
813
|
+
email: z.string(),
|
814
|
+
},
|
815
|
+
response: {
|
816
|
+
bio: z.string(),
|
817
|
+
},
|
818
|
+
});
|
819
|
+
|
820
|
+
server.use(
|
821
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
822
|
+
return new Response("Request failed", { status: 500 });
|
823
|
+
}),
|
824
|
+
);
|
825
|
+
|
826
|
+
await expect(
|
827
|
+
userRequest.send(
|
828
|
+
{
|
829
|
+
name: "John Doe",
|
830
|
+
email: "john@example.com",
|
831
|
+
},
|
832
|
+
{ owner: me },
|
833
|
+
),
|
834
|
+
).rejects.toMatchInlineSnapshot(`
|
835
|
+
{
|
836
|
+
"code": 500,
|
837
|
+
"details": undefined,
|
838
|
+
"message": "Request failed",
|
839
|
+
}
|
840
|
+
`);
|
841
|
+
});
|
842
|
+
|
843
|
+
it("should throw error when HTTP request fails", async () => {
|
844
|
+
const { me, worker } = await setupAccounts();
|
845
|
+
|
846
|
+
const userRequest = experimental_defineRequest({
|
847
|
+
url: "https://api.example.com/api/user",
|
848
|
+
workerId: worker.id,
|
849
|
+
request: {
|
850
|
+
name: z.string(),
|
851
|
+
email: z.string(),
|
852
|
+
},
|
853
|
+
response: {
|
854
|
+
bio: z.string(),
|
855
|
+
},
|
856
|
+
});
|
857
|
+
|
858
|
+
server.close();
|
859
|
+
|
860
|
+
await expect(
|
861
|
+
userRequest.send(
|
862
|
+
{
|
863
|
+
name: "John Doe",
|
864
|
+
email: "john@example.com",
|
865
|
+
},
|
866
|
+
{ owner: me },
|
867
|
+
),
|
868
|
+
).rejects.toThrow("fetch failed");
|
869
|
+
|
870
|
+
server.listen();
|
871
|
+
});
|
872
|
+
});
|
873
|
+
|
874
|
+
describe("User-defined errors from examples", () => {
|
875
|
+
it("should handle user-defined errors", async () => {
|
876
|
+
const { me, worker } = await setupAccounts();
|
877
|
+
|
878
|
+
const userRequest = experimental_defineRequest({
|
879
|
+
url: "https://api.example.com/api/user",
|
880
|
+
workerId: worker.id,
|
881
|
+
request: {
|
882
|
+
name: z.string(),
|
883
|
+
email: z.string(),
|
884
|
+
},
|
885
|
+
response: {
|
886
|
+
bio: z.string(),
|
887
|
+
},
|
888
|
+
});
|
889
|
+
|
890
|
+
server.use(
|
891
|
+
http.post("https://api.example.com/api/user", async ({ request }) => {
|
892
|
+
return userRequest.handle(request, worker, async (user, madeBy) => {
|
893
|
+
throw new JazzRequestError("Custom server error", 400, {
|
894
|
+
detail: "Some details",
|
895
|
+
});
|
896
|
+
});
|
897
|
+
}),
|
898
|
+
);
|
899
|
+
|
900
|
+
await expect(
|
901
|
+
userRequest.send(
|
902
|
+
{
|
903
|
+
name: "John Doe",
|
904
|
+
email: "john@example.com",
|
905
|
+
},
|
906
|
+
{ owner: me },
|
907
|
+
),
|
908
|
+
).rejects.toMatchInlineSnapshot(`
|
909
|
+
{
|
910
|
+
"code": 400,
|
911
|
+
"details": {
|
912
|
+
"detail": "Some details",
|
913
|
+
},
|
914
|
+
"message": "Custom server error",
|
915
|
+
}
|
916
|
+
`);
|
917
|
+
});
|
918
|
+
});
|
919
|
+
|
920
|
+
describe("JazzRequestError class", () => {
|
921
|
+
it("should create JazzRequestError with correct properties", () => {
|
922
|
+
const error = new JazzRequestError("Test error", 400, { detail: "test" });
|
923
|
+
|
924
|
+
expect(error.message).toBe("Test error");
|
925
|
+
expect(error.code).toBe(400);
|
926
|
+
expect(error.details).toEqual({ detail: "test" });
|
927
|
+
expect(error.isJazzRequestError).toBe(true);
|
928
|
+
});
|
929
|
+
|
930
|
+
it("should serialize to JSON correctly", () => {
|
931
|
+
const error = new JazzRequestError("Test error", 400, { detail: "test" });
|
932
|
+
const json = error.toJSON();
|
933
|
+
|
934
|
+
expect(json).toEqual({
|
935
|
+
message: "Test error",
|
936
|
+
code: 400,
|
937
|
+
details: { detail: "test" },
|
938
|
+
});
|
939
|
+
});
|
940
|
+
|
941
|
+
it("should be identified by isJazzRequestError function", () => {
|
942
|
+
const error = new JazzRequestError("Test error", 400);
|
943
|
+
const regularError = new Error("Regular error");
|
944
|
+
|
945
|
+
expect(isJazzRequestError(error)).toBe(true);
|
946
|
+
expect(isJazzRequestError(regularError)).toBe(false);
|
947
|
+
expect(isJazzRequestError({ isJazzRequestError: true })).toBe(true);
|
948
|
+
expect(isJazzRequestError(null)).toBe(false);
|
949
|
+
});
|
950
|
+
});
|
951
|
+
});
|