@ekodb/ekodb-client 0.7.0 → 0.7.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/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/query-builder.test.d.ts +4 -0
- package/dist/query-builder.test.js +318 -0
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +3 -3
- package/dist/utils.test.d.ts +4 -0
- package/dist/utils.test.js +411 -0
- package/package.json +7 -4
- package/src/client.ts +1 -1
- package/src/query-builder.test.ts +404 -0
- package/src/utils.test.ts +506 -0
- package/src/utils.ts +3 -3
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for ekoDB TypeScript client QueryBuilder
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const query_builder_1 = require("./query-builder");
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Basic Tests
|
|
10
|
+
// ============================================================================
|
|
11
|
+
(0, vitest_1.describe)("QueryBuilder basics", () => {
|
|
12
|
+
(0, vitest_1.it)("creates empty query builder", () => {
|
|
13
|
+
const qb = new query_builder_1.QueryBuilder();
|
|
14
|
+
(0, vitest_1.expect)(qb).toBeInstanceOf(query_builder_1.QueryBuilder);
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("builds empty query", () => {
|
|
17
|
+
const query = new query_builder_1.QueryBuilder().build();
|
|
18
|
+
(0, vitest_1.expect)(query).toEqual({});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Equality Operators Tests
|
|
23
|
+
// ============================================================================
|
|
24
|
+
(0, vitest_1.describe)("QueryBuilder equality operators", () => {
|
|
25
|
+
(0, vitest_1.it)("builds eq filter", () => {
|
|
26
|
+
const query = new query_builder_1.QueryBuilder().eq("status", "active").build();
|
|
27
|
+
(0, vitest_1.expect)(query.filter).toEqual({
|
|
28
|
+
type: "Condition",
|
|
29
|
+
content: {
|
|
30
|
+
field: "status",
|
|
31
|
+
operator: "Eq",
|
|
32
|
+
value: "active",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)("builds ne filter", () => {
|
|
37
|
+
const query = new query_builder_1.QueryBuilder().ne("status", "deleted").build();
|
|
38
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Ne");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Comparison Operators Tests
|
|
43
|
+
// ============================================================================
|
|
44
|
+
(0, vitest_1.describe)("QueryBuilder comparison operators", () => {
|
|
45
|
+
(0, vitest_1.it)("builds gt filter", () => {
|
|
46
|
+
const query = new query_builder_1.QueryBuilder().gt("age", 18).build();
|
|
47
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Gt");
|
|
48
|
+
(0, vitest_1.expect)(query.filter.content.value).toBe(18);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("builds gte filter", () => {
|
|
51
|
+
const query = new query_builder_1.QueryBuilder().gte("score", 80).build();
|
|
52
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Gte");
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.it)("builds lt filter", () => {
|
|
55
|
+
const query = new query_builder_1.QueryBuilder().lt("price", 100.5).build();
|
|
56
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Lt");
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.it)("builds lte filter", () => {
|
|
59
|
+
const query = new query_builder_1.QueryBuilder().lte("count", 1000).build();
|
|
60
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Lte");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Array Operators Tests
|
|
65
|
+
// ============================================================================
|
|
66
|
+
(0, vitest_1.describe)("QueryBuilder array operators", () => {
|
|
67
|
+
(0, vitest_1.it)("builds in filter", () => {
|
|
68
|
+
const query = new query_builder_1.QueryBuilder()
|
|
69
|
+
.in("status", ["active", "pending"])
|
|
70
|
+
.build();
|
|
71
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("In");
|
|
72
|
+
(0, vitest_1.expect)(query.filter.content.value).toEqual(["active", "pending"]);
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("builds nin filter", () => {
|
|
75
|
+
const query = new query_builder_1.QueryBuilder()
|
|
76
|
+
.nin("role", ["blocked", "deleted"])
|
|
77
|
+
.build();
|
|
78
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("NotIn");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// String Operators Tests
|
|
83
|
+
// ============================================================================
|
|
84
|
+
(0, vitest_1.describe)("QueryBuilder string operators", () => {
|
|
85
|
+
(0, vitest_1.it)("builds contains filter", () => {
|
|
86
|
+
const query = new query_builder_1.QueryBuilder().contains("email", "@example.com").build();
|
|
87
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Contains");
|
|
88
|
+
(0, vitest_1.expect)(query.filter.content.value).toBe("@example.com");
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("builds startsWith filter", () => {
|
|
91
|
+
const query = new query_builder_1.QueryBuilder().startsWith("name", "John").build();
|
|
92
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("StartsWith");
|
|
93
|
+
});
|
|
94
|
+
(0, vitest_1.it)("builds endsWith filter", () => {
|
|
95
|
+
const query = new query_builder_1.QueryBuilder().endsWith("filename", ".pdf").build();
|
|
96
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("EndsWith");
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)("builds regex filter", () => {
|
|
99
|
+
const query = new query_builder_1.QueryBuilder().regex("phone", "^\\+1").build();
|
|
100
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Regex");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Logical Operators Tests
|
|
105
|
+
// ============================================================================
|
|
106
|
+
(0, vitest_1.describe)("QueryBuilder logical operators", () => {
|
|
107
|
+
(0, vitest_1.it)("builds and filter", () => {
|
|
108
|
+
const conditions = [
|
|
109
|
+
{
|
|
110
|
+
type: "Condition",
|
|
111
|
+
content: { field: "status", operator: "Eq", value: "active" },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: "Condition",
|
|
115
|
+
content: { field: "age", operator: "Gt", value: 18 },
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
const query = new query_builder_1.QueryBuilder().and(conditions).build();
|
|
119
|
+
(0, vitest_1.expect)(query.filter.type).toBe("Logical");
|
|
120
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("And");
|
|
121
|
+
(0, vitest_1.expect)(query.filter.content.expressions).toHaveLength(2);
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.it)("builds or filter", () => {
|
|
124
|
+
const conditions = [
|
|
125
|
+
{
|
|
126
|
+
type: "Condition",
|
|
127
|
+
content: { field: "role", operator: "Eq", value: "admin" },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: "Condition",
|
|
131
|
+
content: { field: "role", operator: "Eq", value: "super_admin" },
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
const query = new query_builder_1.QueryBuilder().or(conditions).build();
|
|
135
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Or");
|
|
136
|
+
});
|
|
137
|
+
(0, vitest_1.it)("builds not filter", () => {
|
|
138
|
+
const condition = {
|
|
139
|
+
type: "Condition",
|
|
140
|
+
content: { field: "deleted", operator: "Eq", value: true },
|
|
141
|
+
};
|
|
142
|
+
const query = new query_builder_1.QueryBuilder().not(condition).build();
|
|
143
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("Not");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Multiple Filters (Auto AND) Tests
|
|
148
|
+
// ============================================================================
|
|
149
|
+
(0, vitest_1.describe)("QueryBuilder multiple filters", () => {
|
|
150
|
+
(0, vitest_1.it)("combines multiple filters with AND logic", () => {
|
|
151
|
+
const query = new query_builder_1.QueryBuilder()
|
|
152
|
+
.eq("status", "active")
|
|
153
|
+
.gt("age", 18)
|
|
154
|
+
.contains("email", "@company.com")
|
|
155
|
+
.build();
|
|
156
|
+
(0, vitest_1.expect)(query.filter.type).toBe("Logical");
|
|
157
|
+
(0, vitest_1.expect)(query.filter.content.operator).toBe("And");
|
|
158
|
+
(0, vitest_1.expect)(query.filter.content.expressions).toHaveLength(3);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Sorting Tests
|
|
163
|
+
// ============================================================================
|
|
164
|
+
(0, vitest_1.describe)("QueryBuilder sorting", () => {
|
|
165
|
+
(0, vitest_1.it)("builds ascending sort", () => {
|
|
166
|
+
const query = new query_builder_1.QueryBuilder().sortAsc("name").build();
|
|
167
|
+
(0, vitest_1.expect)(query.sort).toHaveLength(1);
|
|
168
|
+
(0, vitest_1.expect)(query.sort[0].field).toBe("name");
|
|
169
|
+
(0, vitest_1.expect)(query.sort[0].ascending).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
(0, vitest_1.it)("builds descending sort", () => {
|
|
172
|
+
const query = new query_builder_1.QueryBuilder().sortDesc("created_at").build();
|
|
173
|
+
(0, vitest_1.expect)(query.sort[0].ascending).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
(0, vitest_1.it)("builds multiple sorts", () => {
|
|
176
|
+
const query = new query_builder_1.QueryBuilder()
|
|
177
|
+
.sortDesc("created_at")
|
|
178
|
+
.sortAsc("name")
|
|
179
|
+
.build();
|
|
180
|
+
(0, vitest_1.expect)(query.sort).toHaveLength(2);
|
|
181
|
+
(0, vitest_1.expect)(query.sort[0].field).toBe("created_at");
|
|
182
|
+
(0, vitest_1.expect)(query.sort[0].ascending).toBe(false);
|
|
183
|
+
(0, vitest_1.expect)(query.sort[1].field).toBe("name");
|
|
184
|
+
(0, vitest_1.expect)(query.sort[1].ascending).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Pagination Tests
|
|
189
|
+
// ============================================================================
|
|
190
|
+
(0, vitest_1.describe)("QueryBuilder pagination", () => {
|
|
191
|
+
(0, vitest_1.it)("builds limit", () => {
|
|
192
|
+
const query = new query_builder_1.QueryBuilder().limit(10).build();
|
|
193
|
+
(0, vitest_1.expect)(query.limit).toBe(10);
|
|
194
|
+
});
|
|
195
|
+
(0, vitest_1.it)("builds skip", () => {
|
|
196
|
+
const query = new query_builder_1.QueryBuilder().skip(20).build();
|
|
197
|
+
(0, vitest_1.expect)(query.skip).toBe(20);
|
|
198
|
+
});
|
|
199
|
+
(0, vitest_1.it)("builds page (convenience method)", () => {
|
|
200
|
+
// Page 2 with 20 items per page = skip 40
|
|
201
|
+
const query = new query_builder_1.QueryBuilder().page(2, 20).build();
|
|
202
|
+
(0, vitest_1.expect)(query.limit).toBe(20);
|
|
203
|
+
(0, vitest_1.expect)(query.skip).toBe(40);
|
|
204
|
+
});
|
|
205
|
+
(0, vitest_1.it)("builds page 0", () => {
|
|
206
|
+
const query = new query_builder_1.QueryBuilder().page(0, 10).build();
|
|
207
|
+
(0, vitest_1.expect)(query.skip).toBe(0);
|
|
208
|
+
(0, vitest_1.expect)(query.limit).toBe(10);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Join Tests
|
|
213
|
+
// ============================================================================
|
|
214
|
+
(0, vitest_1.describe)("QueryBuilder join", () => {
|
|
215
|
+
(0, vitest_1.it)("builds join configuration", () => {
|
|
216
|
+
const joinConfig = {
|
|
217
|
+
collections: ["users"],
|
|
218
|
+
local_field: "user_id",
|
|
219
|
+
foreign_field: "id",
|
|
220
|
+
as_field: "user",
|
|
221
|
+
};
|
|
222
|
+
const query = new query_builder_1.QueryBuilder().join(joinConfig).build();
|
|
223
|
+
(0, vitest_1.expect)(query.join).toEqual(joinConfig);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Bypass Flags Tests
|
|
228
|
+
// ============================================================================
|
|
229
|
+
(0, vitest_1.describe)("QueryBuilder bypass flags", () => {
|
|
230
|
+
(0, vitest_1.it)("builds bypass_cache true", () => {
|
|
231
|
+
const query = new query_builder_1.QueryBuilder().bypassCache(true).build();
|
|
232
|
+
(0, vitest_1.expect)(query.bypass_cache).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
(0, vitest_1.it)("builds bypass_cache false (not included)", () => {
|
|
235
|
+
const query = new query_builder_1.QueryBuilder().bypassCache(false).build();
|
|
236
|
+
(0, vitest_1.expect)(query.bypass_cache).toBeUndefined();
|
|
237
|
+
});
|
|
238
|
+
(0, vitest_1.it)("builds bypass_cache default (true)", () => {
|
|
239
|
+
const query = new query_builder_1.QueryBuilder().bypassCache().build();
|
|
240
|
+
(0, vitest_1.expect)(query.bypass_cache).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
(0, vitest_1.it)("builds bypass_ripple", () => {
|
|
243
|
+
const query = new query_builder_1.QueryBuilder().bypassRipple(true).build();
|
|
244
|
+
(0, vitest_1.expect)(query.bypass_ripple).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Chaining Tests
|
|
249
|
+
// ============================================================================
|
|
250
|
+
(0, vitest_1.describe)("QueryBuilder chaining", () => {
|
|
251
|
+
(0, vitest_1.it)("supports full method chaining", () => {
|
|
252
|
+
const query = new query_builder_1.QueryBuilder()
|
|
253
|
+
.eq("status", "active")
|
|
254
|
+
.gt("age", 18)
|
|
255
|
+
.sortDesc("created_at")
|
|
256
|
+
.sortAsc("name")
|
|
257
|
+
.limit(10)
|
|
258
|
+
.skip(20)
|
|
259
|
+
.bypassCache(true)
|
|
260
|
+
.build();
|
|
261
|
+
// Check filter exists
|
|
262
|
+
(0, vitest_1.expect)(query.filter).toBeDefined();
|
|
263
|
+
// Check sort exists
|
|
264
|
+
(0, vitest_1.expect)(query.sort).toHaveLength(2);
|
|
265
|
+
// Check pagination
|
|
266
|
+
(0, vitest_1.expect)(query.limit).toBe(10);
|
|
267
|
+
(0, vitest_1.expect)(query.skip).toBe(20);
|
|
268
|
+
// Check bypass flag
|
|
269
|
+
(0, vitest_1.expect)(query.bypass_cache).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
(0, vitest_1.it)("returns this for method chaining", () => {
|
|
272
|
+
const qb = new query_builder_1.QueryBuilder();
|
|
273
|
+
(0, vitest_1.expect)(qb.eq("a", 1)).toBe(qb);
|
|
274
|
+
(0, vitest_1.expect)(qb.ne("b", 2)).toBe(qb);
|
|
275
|
+
(0, vitest_1.expect)(qb.gt("c", 3)).toBe(qb);
|
|
276
|
+
(0, vitest_1.expect)(qb.gte("d", 4)).toBe(qb);
|
|
277
|
+
(0, vitest_1.expect)(qb.lt("e", 5)).toBe(qb);
|
|
278
|
+
(0, vitest_1.expect)(qb.lte("f", 6)).toBe(qb);
|
|
279
|
+
(0, vitest_1.expect)(qb.in("g", [7])).toBe(qb);
|
|
280
|
+
(0, vitest_1.expect)(qb.nin("h", [8])).toBe(qb);
|
|
281
|
+
(0, vitest_1.expect)(qb.contains("i", "j")).toBe(qb);
|
|
282
|
+
(0, vitest_1.expect)(qb.startsWith("k", "l")).toBe(qb);
|
|
283
|
+
(0, vitest_1.expect)(qb.endsWith("m", "n")).toBe(qb);
|
|
284
|
+
(0, vitest_1.expect)(qb.regex("o", "p")).toBe(qb);
|
|
285
|
+
(0, vitest_1.expect)(qb.sortAsc("q")).toBe(qb);
|
|
286
|
+
(0, vitest_1.expect)(qb.sortDesc("r")).toBe(qb);
|
|
287
|
+
(0, vitest_1.expect)(qb.limit(1)).toBe(qb);
|
|
288
|
+
(0, vitest_1.expect)(qb.skip(0)).toBe(qb);
|
|
289
|
+
(0, vitest_1.expect)(qb.bypassCache()).toBe(qb);
|
|
290
|
+
(0, vitest_1.expect)(qb.bypassRipple()).toBe(qb);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// Raw Filter Tests
|
|
295
|
+
// ============================================================================
|
|
296
|
+
(0, vitest_1.describe)("QueryBuilder rawFilter", () => {
|
|
297
|
+
(0, vitest_1.it)("adds raw filter expression", () => {
|
|
298
|
+
const rawFilter = {
|
|
299
|
+
type: "Condition",
|
|
300
|
+
content: {
|
|
301
|
+
field: "custom",
|
|
302
|
+
operator: "CustomOp",
|
|
303
|
+
value: "custom_value",
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
const query = new query_builder_1.QueryBuilder().rawFilter(rawFilter).build();
|
|
307
|
+
(0, vitest_1.expect)(query.filter).toEqual(rawFilter);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// SortOrder Enum Tests
|
|
312
|
+
// ============================================================================
|
|
313
|
+
(0, vitest_1.describe)("SortOrder enum", () => {
|
|
314
|
+
(0, vitest_1.it)("has correct values", () => {
|
|
315
|
+
(0, vitest_1.expect)(query_builder_1.SortOrder.Asc).toBe("asc");
|
|
316
|
+
(0, vitest_1.expect)(query_builder_1.SortOrder.Desc).toBe("desc");
|
|
317
|
+
});
|
|
318
|
+
});
|
package/dist/utils.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
14
|
-
* const user = await client.
|
|
14
|
+
* const user = await client.findById('users', userId);
|
|
15
15
|
* const email = getValue(user.email); // Extracts string from { type: 'String', value: 'user@example.com' }
|
|
16
16
|
* const age = getValue(user.age); // Extracts number from { type: 'Integer', value: 25 }
|
|
17
17
|
* ```
|
|
@@ -27,7 +27,7 @@ export declare function getValue<T = any>(field: any): T;
|
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* ```typescript
|
|
30
|
-
* const user = await client.
|
|
30
|
+
* const user = await client.findById('users', userId);
|
|
31
31
|
* const { email, first_name, status } = getValues(user, ['email', 'first_name', 'status']);
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
@@ -81,7 +81,7 @@ export declare function getObjectValue<T = any>(field: any): T | null;
|
|
|
81
81
|
*
|
|
82
82
|
* @example
|
|
83
83
|
* ```typescript
|
|
84
|
-
* const user = await client.
|
|
84
|
+
* const user = await client.findById('users', userId);
|
|
85
85
|
* const plainUser = extractRecord(user);
|
|
86
86
|
* // { id: '123', email: 'user@example.com', first_name: 'John', ... }
|
|
87
87
|
* ```
|
package/dist/utils.js
CHANGED
|
@@ -27,7 +27,7 @@ exports.extractRecord = extractRecord;
|
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* ```typescript
|
|
30
|
-
* const user = await client.
|
|
30
|
+
* const user = await client.findById('users', userId);
|
|
31
31
|
* const email = getValue(user.email); // Extracts string from { type: 'String', value: 'user@example.com' }
|
|
32
32
|
* const age = getValue(user.age); // Extracts number from { type: 'Integer', value: 25 }
|
|
33
33
|
* ```
|
|
@@ -48,7 +48,7 @@ function getValue(field) {
|
|
|
48
48
|
*
|
|
49
49
|
* @example
|
|
50
50
|
* ```typescript
|
|
51
|
-
* const user = await client.
|
|
51
|
+
* const user = await client.findById('users', userId);
|
|
52
52
|
* const { email, first_name, status } = getValues(user, ['email', 'first_name', 'status']);
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
@@ -177,7 +177,7 @@ function getObjectValue(field) {
|
|
|
177
177
|
*
|
|
178
178
|
* @example
|
|
179
179
|
* ```typescript
|
|
180
|
-
* const user = await client.
|
|
180
|
+
* const user = await client.findById('users', userId);
|
|
181
181
|
* const plainUser = extractRecord(user);
|
|
182
182
|
* // { id: '123', email: 'user@example.com', first_name: 'John', ... }
|
|
183
183
|
* ```
|