typed-bridge 2.1.5 → 3.0.1
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/dist/bridge/index.d.ts +9 -4
- package/dist/bridge/index.js +32 -4
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +2 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -1
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +119 -0
- package/dist/scripts/buildTypeBridge.js +34 -1
- package/dist/scripts/typedBridgeCleaner.js +247 -28
- package/dist/tools/index.d.ts +126 -0
- package/dist/tools/index.js +218 -0
- package/package.json +18 -3
- package/readme.md +203 -234
- package/.vscode/settings.json +0 -3
- package/dist/demo/bridge/index.d.ts +0 -255
- package/dist/demo/bridge/index.js +0 -56
- package/dist/demo/bridge/order/index.d.ts +0 -42
- package/dist/demo/bridge/order/index.js +0 -165
- package/dist/demo/bridge/order/types.d.ts +0 -187
- package/dist/demo/bridge/order/types.js +0 -150
- package/dist/demo/bridge/product/index.d.ts +0 -16
- package/dist/demo/bridge/product/index.js +0 -67
- package/dist/demo/bridge/product/types.d.ts +0 -24
- package/dist/demo/bridge/product/types.js +0 -27
- package/dist/demo/bridge/user/index.d.ts +0 -28
- package/dist/demo/bridge/user/index.js +0 -86
- package/dist/demo/bridge/user/types.d.ts +0 -23
- package/dist/demo/bridge/user/types.js +0 -26
- package/dist/demo/index.d.ts +0 -1
- package/dist/demo/index.js +0 -23
- package/dist/demo/middleware.d.ts +0 -1
- package/dist/demo/middleware.js +0 -58
- package/test/bridge.ts +0 -283
- package/test/index.ts +0 -251
package/dist/demo/middleware.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const __1 = require("..");
|
|
4
|
-
// Global middleware — runs for every route (lowest specificity)
|
|
5
|
-
(0, __1.createMiddleware)('*', async (req, res) => {
|
|
6
|
-
return {
|
|
7
|
-
context: {
|
|
8
|
-
requestedAt: Date.now()
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
});
|
|
12
|
-
// Auth middleware — runs for all user.* routes
|
|
13
|
-
(0, __1.createMiddleware)('user.*', async (req, res) => {
|
|
14
|
-
const token = req.headers.authorization;
|
|
15
|
-
if (!token || !token.startsWith('Bearer ')) {
|
|
16
|
-
res.status(401).send('Unauthorized');
|
|
17
|
-
return { next: false };
|
|
18
|
-
}
|
|
19
|
-
const userId = parseInt(token.split(' ')[1]) || 1;
|
|
20
|
-
return {
|
|
21
|
-
context: { userId }
|
|
22
|
-
};
|
|
23
|
-
});
|
|
24
|
-
// Auth middleware — runs for all product.* routes
|
|
25
|
-
(0, __1.createMiddleware)('product.*', async (req, res) => {
|
|
26
|
-
const token = req.headers.authorization;
|
|
27
|
-
if (!token || !token.startsWith('Bearer ')) {
|
|
28
|
-
res.status(401).send('Unauthorized');
|
|
29
|
-
return { next: false };
|
|
30
|
-
}
|
|
31
|
-
const userId = parseInt(token.split(' ')[1]) || 1;
|
|
32
|
-
return {
|
|
33
|
-
context: { userId }
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
// Auth middleware — runs for all order.* routes
|
|
37
|
-
(0, __1.createMiddleware)('order.*', async (req, res) => {
|
|
38
|
-
const token = req.headers.authorization;
|
|
39
|
-
if (!token || !token.startsWith('Bearer ')) {
|
|
40
|
-
res.status(401).send('Unauthorized');
|
|
41
|
-
return { next: false };
|
|
42
|
-
}
|
|
43
|
-
const userId = parseInt(token.split(' ')[1]) || 1;
|
|
44
|
-
return {
|
|
45
|
-
context: { userId }
|
|
46
|
-
};
|
|
47
|
-
});
|
|
48
|
-
// Admin middleware — runs only for user.remove (highest specificity)
|
|
49
|
-
(0, __1.createMiddleware)('user.remove', async (req, res) => {
|
|
50
|
-
const isAdmin = req.headers['x-admin'] === 'true';
|
|
51
|
-
if (!isAdmin) {
|
|
52
|
-
res.status(403).send('Admin access required');
|
|
53
|
-
return { next: false };
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
context: { isAdmin: true }
|
|
57
|
-
};
|
|
58
|
-
});
|
package/test/bridge.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
interface User {
|
|
3
|
-
id: number;
|
|
4
|
-
name: string;
|
|
5
|
-
email: string;
|
|
6
|
-
createdAt: Date;
|
|
7
|
-
}
|
|
8
|
-
interface Product {
|
|
9
|
-
id: number;
|
|
10
|
-
name: string;
|
|
11
|
-
price: number;
|
|
12
|
-
createdAt: Date;
|
|
13
|
-
}
|
|
14
|
-
interface OrderItem {
|
|
15
|
-
productId: number;
|
|
16
|
-
quantity: number;
|
|
17
|
-
price: number;
|
|
18
|
-
notes?: string;
|
|
19
|
-
discount: number | null;
|
|
20
|
-
}
|
|
21
|
-
interface Address {
|
|
22
|
-
street: string;
|
|
23
|
-
city: string;
|
|
24
|
-
state: string;
|
|
25
|
-
zip: string;
|
|
26
|
-
country: string;
|
|
27
|
-
}
|
|
28
|
-
interface Order {
|
|
29
|
-
id: number;
|
|
30
|
-
customerId: number;
|
|
31
|
-
status: string;
|
|
32
|
-
total: number;
|
|
33
|
-
items: OrderItem[];
|
|
34
|
-
shippingAddress: Address;
|
|
35
|
-
billingAddress: Address | null;
|
|
36
|
-
isGift: boolean;
|
|
37
|
-
giftMessage: string | null;
|
|
38
|
-
createdAt: Date;
|
|
39
|
-
updatedAt: Date | null;
|
|
40
|
-
}
|
|
41
|
-
declare const _default: {
|
|
42
|
-
'user.fetch': (args: {
|
|
43
|
-
id: number;
|
|
44
|
-
}) => Promise<{
|
|
45
|
-
id: number;
|
|
46
|
-
name: string;
|
|
47
|
-
email: string;
|
|
48
|
-
}>;
|
|
49
|
-
'user.create': (args: {
|
|
50
|
-
name: string;
|
|
51
|
-
email: string;
|
|
52
|
-
}) => Promise<{
|
|
53
|
-
id: number;
|
|
54
|
-
name: string;
|
|
55
|
-
email: string;
|
|
56
|
-
createdAt: Date;
|
|
57
|
-
}>;
|
|
58
|
-
'user.update': (args: {
|
|
59
|
-
id: number;
|
|
60
|
-
name?: string;
|
|
61
|
-
email?: string;
|
|
62
|
-
}) => Promise<User>;
|
|
63
|
-
'user.remove': (args: {
|
|
64
|
-
id: number;
|
|
65
|
-
}) => Promise<{
|
|
66
|
-
success: boolean;
|
|
67
|
-
}>;
|
|
68
|
-
'user.fetchAll': () => Promise<User[]>;
|
|
69
|
-
'product.fetch': (args: {
|
|
70
|
-
id: number;
|
|
71
|
-
}) => Promise<{
|
|
72
|
-
id: number;
|
|
73
|
-
name: string;
|
|
74
|
-
price: number;
|
|
75
|
-
createdAt: Date;
|
|
76
|
-
}>;
|
|
77
|
-
'product.create': (args: {
|
|
78
|
-
name: string;
|
|
79
|
-
price: number;
|
|
80
|
-
}) => Promise<{
|
|
81
|
-
id: number;
|
|
82
|
-
name: string;
|
|
83
|
-
price: number;
|
|
84
|
-
createdAt: Date;
|
|
85
|
-
}>;
|
|
86
|
-
'product.list': () => Promise<Product[]>;
|
|
87
|
-
'order.create': (args: {
|
|
88
|
-
customerId: number;
|
|
89
|
-
items: {
|
|
90
|
-
productId: number;
|
|
91
|
-
quantity: number;
|
|
92
|
-
price: number;
|
|
93
|
-
notes?: string;
|
|
94
|
-
discount: number | null;
|
|
95
|
-
}[];
|
|
96
|
-
shippingAddress: {
|
|
97
|
-
street: string;
|
|
98
|
-
city: string;
|
|
99
|
-
state: string;
|
|
100
|
-
zip: string;
|
|
101
|
-
country: string;
|
|
102
|
-
};
|
|
103
|
-
billingAddress: {
|
|
104
|
-
street: string;
|
|
105
|
-
city: string;
|
|
106
|
-
state: string;
|
|
107
|
-
zip: string;
|
|
108
|
-
country: string;
|
|
109
|
-
} | null;
|
|
110
|
-
couponCode?: string | null;
|
|
111
|
-
scheduledDate?: Date;
|
|
112
|
-
isGift: boolean;
|
|
113
|
-
giftMessage: string | null;
|
|
114
|
-
}) => Promise<{
|
|
115
|
-
id: number;
|
|
116
|
-
status: string;
|
|
117
|
-
total: number;
|
|
118
|
-
items: {
|
|
119
|
-
productId: number;
|
|
120
|
-
quantity: number;
|
|
121
|
-
price: number;
|
|
122
|
-
notes?: string;
|
|
123
|
-
discount: number | null;
|
|
124
|
-
}[];
|
|
125
|
-
createdAt: Date;
|
|
126
|
-
}>;
|
|
127
|
-
'order.fetch': (args: {
|
|
128
|
-
id: number;
|
|
129
|
-
}) => Promise<{
|
|
130
|
-
id: number;
|
|
131
|
-
customerId: number;
|
|
132
|
-
status: string;
|
|
133
|
-
total: number;
|
|
134
|
-
items: {
|
|
135
|
-
productId: number;
|
|
136
|
-
quantity: number;
|
|
137
|
-
price: number;
|
|
138
|
-
notes?: string;
|
|
139
|
-
discount: number | null;
|
|
140
|
-
}[];
|
|
141
|
-
shippingAddress: {
|
|
142
|
-
street: string;
|
|
143
|
-
city: string;
|
|
144
|
-
state: string;
|
|
145
|
-
zip: string;
|
|
146
|
-
country: string;
|
|
147
|
-
};
|
|
148
|
-
billingAddress: {
|
|
149
|
-
street: string;
|
|
150
|
-
city: string;
|
|
151
|
-
state: string;
|
|
152
|
-
zip: string;
|
|
153
|
-
country: string;
|
|
154
|
-
} | null;
|
|
155
|
-
isGift: boolean;
|
|
156
|
-
giftMessage: string | null;
|
|
157
|
-
createdAt: Date;
|
|
158
|
-
updatedAt: Date | null;
|
|
159
|
-
}>;
|
|
160
|
-
'order.update': (args: {
|
|
161
|
-
id: number;
|
|
162
|
-
status?: string;
|
|
163
|
-
shippingAddress?: {
|
|
164
|
-
street: string;
|
|
165
|
-
city: string;
|
|
166
|
-
state: string;
|
|
167
|
-
zip: string;
|
|
168
|
-
country: string;
|
|
169
|
-
};
|
|
170
|
-
giftMessage?: string | null;
|
|
171
|
-
}) => Promise<{
|
|
172
|
-
id: number;
|
|
173
|
-
status: string;
|
|
174
|
-
updatedAt: Date;
|
|
175
|
-
}>;
|
|
176
|
-
'order.list': () => Promise<Order[]>;
|
|
177
|
-
'order.resolve': (args: {
|
|
178
|
-
id: number;
|
|
179
|
-
}) => Promise<{
|
|
180
|
-
status: "found";
|
|
181
|
-
order: {
|
|
182
|
-
id: number;
|
|
183
|
-
customerName: string;
|
|
184
|
-
orderStatus: "pending" | "confirmed" | "shipped" | "delivered" | "cancelled";
|
|
185
|
-
total: number;
|
|
186
|
-
};
|
|
187
|
-
} | {
|
|
188
|
-
status: "not_found";
|
|
189
|
-
}>;
|
|
190
|
-
'order.statusFilter': (args: {
|
|
191
|
-
status: "pending" | "confirmed" | "shipped" | "delivered" | "cancelled";
|
|
192
|
-
}) => Promise<{
|
|
193
|
-
orders: {
|
|
194
|
-
id: number;
|
|
195
|
-
status: "pending" | "confirmed" | "shipped" | "delivered" | "cancelled";
|
|
196
|
-
total: number;
|
|
197
|
-
}[];
|
|
198
|
-
}>;
|
|
199
|
-
'order.tag': (args: {
|
|
200
|
-
orderId: number;
|
|
201
|
-
tag: string | number;
|
|
202
|
-
}) => Promise<{
|
|
203
|
-
orderId: number;
|
|
204
|
-
tag: string | number;
|
|
205
|
-
appliedAt: Date;
|
|
206
|
-
}>;
|
|
207
|
-
'order.primitives': (args: {
|
|
208
|
-
key: string;
|
|
209
|
-
}) => Promise<{
|
|
210
|
-
str: string;
|
|
211
|
-
num: number;
|
|
212
|
-
bool: boolean;
|
|
213
|
-
date: Date;
|
|
214
|
-
nul: null;
|
|
215
|
-
undef: undefined;
|
|
216
|
-
unk: unknown;
|
|
217
|
-
whatever: any;
|
|
218
|
-
optStr?: string;
|
|
219
|
-
nullStr: string | null;
|
|
220
|
-
defStr: string;
|
|
221
|
-
optNullStr?: string | null;
|
|
222
|
-
nullOptStr: (string | undefined) | null;
|
|
223
|
-
tags: string[];
|
|
224
|
-
scores: number[] | null;
|
|
225
|
-
optDates?: Date[];
|
|
226
|
-
nested: {
|
|
227
|
-
a: number;
|
|
228
|
-
b?: string;
|
|
229
|
-
c: {
|
|
230
|
-
d: boolean;
|
|
231
|
-
e: string[] | null;
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
}>;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
type typedBridgeConfig = {
|
|
238
|
-
host: string
|
|
239
|
-
headers: { [key: string]: string }
|
|
240
|
-
onResponse: (res: Response) => void
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export const typedBridgeConfig: typedBridgeConfig = {
|
|
244
|
-
host: '',
|
|
245
|
-
headers: { 'Content-Type': 'application/json' },
|
|
246
|
-
onResponse: (res: Response) => {}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export const typedBridge = new Proxy(
|
|
250
|
-
{},
|
|
251
|
-
{
|
|
252
|
-
get(_, methodName: string) {
|
|
253
|
-
return async (args: any) => {
|
|
254
|
-
const response = await fetch(
|
|
255
|
-
typedBridgeConfig.host + (typedBridgeConfig.host.endsWith('/') ? '' : '/') + methodName,
|
|
256
|
-
{
|
|
257
|
-
method: 'POST',
|
|
258
|
-
headers: {
|
|
259
|
-
'Content-Type': 'application/json',
|
|
260
|
-
...typedBridgeConfig.headers
|
|
261
|
-
},
|
|
262
|
-
body: JSON.stringify(args)
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
typedBridgeConfig.onResponse(response)
|
|
267
|
-
|
|
268
|
-
if (!response.ok) {
|
|
269
|
-
const errorText = await response.text()
|
|
270
|
-
console.error('REQ_FAILED', response.url, errorText)
|
|
271
|
-
throw new Error(errorText)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return response.json().catch(error => {
|
|
275
|
-
console.error('RES_NOT_JSON', response.url, error)
|
|
276
|
-
throw new Error(error.message)
|
|
277
|
-
})
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
) as typeof _default
|
|
282
|
-
|
|
283
|
-
export default typedBridge
|
package/test/index.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import bridge, { typedBridgeConfig } from './bridge'
|
|
2
|
-
|
|
3
|
-
typedBridgeConfig.host = 'http://localhost:8080/bridge'
|
|
4
|
-
typedBridgeConfig.headers = {
|
|
5
|
-
'Content-Type': 'application/json',
|
|
6
|
-
Authorization: 'Bearer 123'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let passed = 0
|
|
10
|
-
let failed = 0
|
|
11
|
-
|
|
12
|
-
const test = async (name: string, fn: () => Promise<void>) => {
|
|
13
|
-
try {
|
|
14
|
-
await fn()
|
|
15
|
-
passed++
|
|
16
|
-
console.log(` ✅ ${name}`)
|
|
17
|
-
} catch (error: any) {
|
|
18
|
-
failed++
|
|
19
|
-
console.error(` ❌ ${name}: ${error.message}`)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const assert = (condition: boolean, message: string) => {
|
|
24
|
-
if (!condition) throw new Error(message)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const expectError = async (fn: () => Promise<any>, expectedSubstring?: string) => {
|
|
28
|
-
try {
|
|
29
|
-
await fn()
|
|
30
|
-
throw new Error('Expected an error but none was thrown')
|
|
31
|
-
} catch (error: any) {
|
|
32
|
-
if (expectedSubstring && !error.message.includes(expectedSubstring)) {
|
|
33
|
-
throw new Error(`Expected error containing "${expectedSubstring}", got: "${error.message}"`)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const main = async () => {
|
|
39
|
-
console.log('\n--- User routes ---\n')
|
|
40
|
-
|
|
41
|
-
await test('user.fetchAll returns users', async () => {
|
|
42
|
-
const users = await bridge['user.fetchAll']()
|
|
43
|
-
assert(users.length >= 3, `Expected at least 3 users, got ${users.length}`)
|
|
44
|
-
assert(typeof users[0].id === 'number', `Expected numeric id`)
|
|
45
|
-
assert(typeof users[0].name === 'string', `Expected string name`)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
await test('user.fetch returns a user by id', async () => {
|
|
49
|
-
const user = await bridge['user.fetch']({ id: 1 })
|
|
50
|
-
assert(user.id === 1, `Expected id 1, got ${user.id}`)
|
|
51
|
-
assert(typeof user.name === 'string', `Expected string name`)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
await test('user.create adds a new user', async () => {
|
|
55
|
-
const user = await bridge['user.create']({ name: 'Alice', email: 'alice@example.com' })
|
|
56
|
-
assert(user.name === 'Alice', `Expected name "Alice", got "${user.name}"`)
|
|
57
|
-
assert(user.email === 'alice@example.com', `Expected email "alice@example.com"`)
|
|
58
|
-
assert(typeof user.id === 'number', `Expected numeric id`)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
await test('user.update modifies an existing user', async () => {
|
|
62
|
-
const user = await bridge['user.update']({ id: 1, name: 'Neil Updated' })
|
|
63
|
-
assert(user.name === 'Neil Updated', `Expected name "Neil Updated", got "${user.name}"`)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
await test('user.fetch not found throws error', async () => {
|
|
67
|
-
await expectError(() => bridge['user.fetch']({ id: 999 }), 'User with ID 999 not found')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
await test('user.fetch zod validation rejects id < 1', async () => {
|
|
71
|
-
await expectError(() => bridge['user.fetch']({ id: 0 }))
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
await test('user.remove requires admin header', async () => {
|
|
75
|
-
await expectError(() => bridge['user.remove']({ id: 999 }), 'Admin access required')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
await test('user.remove works with admin header', async () => {
|
|
79
|
-
const created = await bridge['user.create']({ name: 'ToRemove', email: 'remove@test.com' })
|
|
80
|
-
typedBridgeConfig.headers['X-Admin'] = 'true'
|
|
81
|
-
const result = await bridge['user.remove']({ id: created.id })
|
|
82
|
-
delete typedBridgeConfig.headers['X-Admin']
|
|
83
|
-
assert(result.success === true, `Expected success: true`)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
console.log('\n--- Product routes ---\n')
|
|
87
|
-
|
|
88
|
-
await test('product.list returns products', async () => {
|
|
89
|
-
const products = await bridge['product.list']()
|
|
90
|
-
assert(products.length >= 3, `Expected at least 3 products, got ${products.length}`)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
await test('product.fetch returns a product by id', async () => {
|
|
94
|
-
const product = await bridge['product.fetch']({ id: 1 })
|
|
95
|
-
assert(product.id === 1, `Expected id 1, got ${product.id}`)
|
|
96
|
-
assert(typeof product.name === 'string', `Expected string name`)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
await test('product.create adds a new product', async () => {
|
|
100
|
-
const product = await bridge['product.create']({ name: 'Mouse', price: 29 })
|
|
101
|
-
assert(product.name === 'Mouse', `Expected "Mouse", got "${product.name}"`)
|
|
102
|
-
assert(product.price === 29, `Expected price 29, got ${product.price}`)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
await test('product.fetch not found throws error', async () => {
|
|
106
|
-
await expectError(() => bridge['product.fetch']({ id: 999 }), 'Product with ID 999 not found')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
await test('product.fetch zod validation rejects id < 1', async () => {
|
|
110
|
-
await expectError(() => bridge['product.fetch']({ id: 0 }))
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
console.log('\n--- Order routes (complex Zod types) ---\n')
|
|
114
|
-
|
|
115
|
-
let createdOrderId: number
|
|
116
|
-
|
|
117
|
-
await test('order.create with nested objects, arrays, nullable, optional', async () => {
|
|
118
|
-
const order = await bridge['order.create']({
|
|
119
|
-
customerId: 1,
|
|
120
|
-
items: [
|
|
121
|
-
{ productId: 1, quantity: 2, price: 999, notes: 'Gift wrap', discount: 50 },
|
|
122
|
-
{ productId: 2, quantity: 1, price: 79, discount: null }
|
|
123
|
-
],
|
|
124
|
-
shippingAddress: { street: '123 Main St', city: 'NYC', state: 'NY', zip: '10001' },
|
|
125
|
-
billingAddress: null,
|
|
126
|
-
couponCode: 'SAVE10',
|
|
127
|
-
isGift: true,
|
|
128
|
-
giftMessage: 'Happy birthday!'
|
|
129
|
-
})
|
|
130
|
-
assert(typeof order.id === 'number', `Expected numeric id`)
|
|
131
|
-
assert(order.status === 'pending', `Expected status "pending"`)
|
|
132
|
-
assert(order.total === 2077, `Expected total 2077, got ${order.total}`)
|
|
133
|
-
assert(order.items.length === 2, `Expected 2 items`)
|
|
134
|
-
assert(order.items[0].notes === 'Gift wrap', `Expected notes`)
|
|
135
|
-
assert(order.items[1].discount === null, `Expected null discount`)
|
|
136
|
-
createdOrderId = order.id
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
await test('order.fetch returns full order with all nested types', async () => {
|
|
140
|
-
const order = await bridge['order.fetch']({ id: createdOrderId })
|
|
141
|
-
assert(order.customerId === 1, `Expected customerId 1`)
|
|
142
|
-
assert(order.shippingAddress.city === 'NYC', `Expected city "NYC"`)
|
|
143
|
-
assert(order.billingAddress === null, `Expected null billingAddress`)
|
|
144
|
-
assert(order.isGift === true, `Expected isGift true`)
|
|
145
|
-
assert(order.giftMessage === 'Happy birthday!', `Expected giftMessage`)
|
|
146
|
-
assert(order.updatedAt === null, `Expected null updatedAt`)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
await test('order.create with optional fields omitted', async () => {
|
|
150
|
-
const order = await bridge['order.create']({
|
|
151
|
-
customerId: 2,
|
|
152
|
-
items: [{ productId: 3, quantity: 1, price: 349, discount: null }],
|
|
153
|
-
shippingAddress: { street: '456 Oak Ave', city: 'LA', state: 'CA', zip: '90001' },
|
|
154
|
-
billingAddress: { street: '789 Elm St', city: 'LA', state: 'CA', zip: '90002' },
|
|
155
|
-
isGift: false,
|
|
156
|
-
giftMessage: null
|
|
157
|
-
})
|
|
158
|
-
assert(order.status === 'pending', `Expected status "pending"`)
|
|
159
|
-
assert(order.items[0].notes === undefined, `Expected undefined notes`)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
await test('order.update with optional nested object', async () => {
|
|
163
|
-
const result = await bridge['order.update']({
|
|
164
|
-
id: createdOrderId,
|
|
165
|
-
status: 'shipped',
|
|
166
|
-
shippingAddress: { street: '999 New St', city: 'SF', state: 'CA', zip: '94101' }
|
|
167
|
-
})
|
|
168
|
-
assert(result.status === 'shipped', `Expected status "shipped"`)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
await test('order.list returns created orders', async () => {
|
|
172
|
-
const orders = await bridge['order.list']()
|
|
173
|
-
assert(orders.length >= 2, `Expected at least 2 orders, got ${orders.length}`)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
await test('order.fetch not found throws error', async () => {
|
|
177
|
-
await expectError(() => bridge['order.fetch']({ id: 999 }), 'Order with ID 999 not found')
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
await test('order.fetch zod validation rejects id < 1', async () => {
|
|
181
|
-
await expectError(() => bridge['order.fetch']({ id: 0 }))
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
await test('order.primitives returns all resolved primitive types', async () => {
|
|
185
|
-
const res = await bridge['order.primitives']({ key: 'test' })
|
|
186
|
-
assert(res.str === 'hello', `Expected str "hello"`)
|
|
187
|
-
assert(res.num === 42, `Expected num 42`)
|
|
188
|
-
assert(res.bool === true, `Expected bool true`)
|
|
189
|
-
assert(typeof res.date === 'string', `Expected date serialized as string`)
|
|
190
|
-
assert(res.nul === null, `Expected nul null`)
|
|
191
|
-
assert(res.undef === undefined || !('undef' in res), `Expected undef undefined`)
|
|
192
|
-
assert(res.whatever === 'literally anything', `Expected any value`)
|
|
193
|
-
assert(res.optStr === 'present', `Expected optStr "present"`)
|
|
194
|
-
assert(res.nullStr === null, `Expected nullStr null`)
|
|
195
|
-
assert(res.defStr === 'default value', `Expected defStr "default value"`)
|
|
196
|
-
assert(res.optNullStr === null, `Expected optNullStr null`)
|
|
197
|
-
assert(Array.isArray(res.tags) && res.tags.length === 3, `Expected 3 tags`)
|
|
198
|
-
assert(Array.isArray(res.scores) && res.scores.length === 3, `Expected 3 scores`)
|
|
199
|
-
assert(Array.isArray(res.optDates) && res.optDates.length === 1, `Expected 1 optDate`)
|
|
200
|
-
assert(res.nested.a === 1, `Expected nested.a === 1`)
|
|
201
|
-
assert(res.nested.b === 'nested', `Expected nested.b === "nested"`)
|
|
202
|
-
assert(res.nested.c.d === true, `Expected nested.c.d === true`)
|
|
203
|
-
assert(Array.isArray(res.nested.c.e) && res.nested.c.e[0] === 'deep', `Expected nested.c.e[0] === "deep"`)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
console.log('\n--- Middleware ---\n')
|
|
207
|
-
|
|
208
|
-
await test('unauthorized request is rejected', async () => {
|
|
209
|
-
const savedAuth = typedBridgeConfig.headers['Authorization']
|
|
210
|
-
delete typedBridgeConfig.headers['Authorization']
|
|
211
|
-
|
|
212
|
-
await expectError(() => bridge['user.fetch']({ id: 1 }), 'Unauthorized')
|
|
213
|
-
|
|
214
|
-
typedBridgeConfig.headers['Authorization'] = savedAuth
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
console.log('\n--- Custom routes ---\n')
|
|
218
|
-
|
|
219
|
-
await test('health endpoint returns status OK', async () => {
|
|
220
|
-
const res = await fetch('http://localhost:8080/bridge/health')
|
|
221
|
-
const data = await res.json() as any
|
|
222
|
-
assert(data.status === 'OK', `Expected status "OK", got "${data.status}"`)
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
await test('custom /status route returns uptime', async () => {
|
|
226
|
-
const res = await fetch('http://localhost:8080/status')
|
|
227
|
-
const data = await res.json() as any
|
|
228
|
-
assert(data.status === 'ok', `Expected status "ok", got "${data.status}"`)
|
|
229
|
-
assert(typeof data.uptime === 'number', `Expected numeric uptime`)
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
console.log('\n--- onResponse callback ---\n')
|
|
233
|
-
|
|
234
|
-
await test('onResponse is called on every request', async () => {
|
|
235
|
-
let callbackFired = false
|
|
236
|
-
typedBridgeConfig.onResponse = () => {
|
|
237
|
-
callbackFired = true
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
await bridge['user.fetchAll']()
|
|
241
|
-
assert(callbackFired, 'onResponse callback was not called')
|
|
242
|
-
|
|
243
|
-
typedBridgeConfig.onResponse = () => {}
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// Summary
|
|
247
|
-
console.log(`\n--- Results: ${passed} passed, ${failed} failed ---\n`)
|
|
248
|
-
process.exit(failed > 0 ? 1 : 0)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
main()
|