@xata.io/client 0.0.0-beta.d0bc5ad → 0.0.0-beta.d2072d7
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/index.d.ts +59 -10
- package/dist/index.js +176 -45
- package/dist/index.test.js +28 -12
- package/dist/util/errors.d.ts +3 -0
- package/dist/util/errors.js +9 -0
- package/package.json +2 -2
- package/src/index.test.ts +34 -14
- package/src/index.ts +204 -43
- package/src/util/errors.ts +6 -0
package/dist/index.d.ts
CHANGED
@@ -54,15 +54,51 @@ export declare const includesAll: (value: string) => Constraint<string>;
|
|
54
54
|
declare type FilterConstraints<T> = {
|
55
55
|
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] | DeepConstraint<T[key]>;
|
56
56
|
};
|
57
|
+
declare type CursorNavigationOptions = {
|
58
|
+
first?: string;
|
59
|
+
} | {
|
60
|
+
last?: string;
|
61
|
+
} | {
|
62
|
+
after?: string;
|
63
|
+
before?: string;
|
64
|
+
};
|
65
|
+
declare type OffsetNavigationOptions = {
|
66
|
+
size?: number;
|
67
|
+
offset?: number;
|
68
|
+
};
|
69
|
+
declare type PaginationOptions = CursorNavigationOptions & OffsetNavigationOptions;
|
57
70
|
declare type BulkQueryOptions<T> = {
|
58
|
-
|
59
|
-
sort?: {
|
60
|
-
column: keyof T;
|
61
|
-
direction?: SortDirection;
|
62
|
-
} | keyof T;
|
71
|
+
page?: PaginationOptions;
|
63
72
|
};
|
64
73
|
declare type QueryOrConstraint<T, R> = Query<T, R> | Constraint<T>;
|
65
|
-
|
74
|
+
declare type QueryMeta = {
|
75
|
+
page: {
|
76
|
+
cursor: string;
|
77
|
+
more: boolean;
|
78
|
+
};
|
79
|
+
};
|
80
|
+
interface BasePage<T, R> {
|
81
|
+
query: Query<T, R>;
|
82
|
+
meta: QueryMeta;
|
83
|
+
records: R[];
|
84
|
+
nextPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
85
|
+
previousPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
86
|
+
firstPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
87
|
+
lastPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
88
|
+
hasNextPage(): boolean;
|
89
|
+
}
|
90
|
+
declare class Page<T, R> implements BasePage<T, R> {
|
91
|
+
readonly query: Query<T, R>;
|
92
|
+
readonly meta: QueryMeta;
|
93
|
+
readonly records: R[];
|
94
|
+
constructor(query: Query<T, R>, meta: QueryMeta, records?: R[]);
|
95
|
+
nextPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
96
|
+
previousPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
97
|
+
firstPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
98
|
+
lastPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
99
|
+
hasNextPage(): boolean;
|
100
|
+
}
|
101
|
+
export declare class Query<T, R = T> implements BasePage<T, R> {
|
66
102
|
table: string;
|
67
103
|
repository: Repository<T>;
|
68
104
|
readonly $any?: QueryOrConstraint<T, R>[];
|
@@ -70,6 +106,9 @@ export declare class Query<T, R = T> {
|
|
70
106
|
readonly $not?: QueryOrConstraint<T, R>[];
|
71
107
|
readonly $none?: QueryOrConstraint<T, R>[];
|
72
108
|
readonly $sort?: Record<string, SortDirection>;
|
109
|
+
readonly query: Query<T, R>;
|
110
|
+
readonly meta: QueryMeta;
|
111
|
+
readonly records: R[];
|
73
112
|
constructor(repository: Repository<T> | null, table: string, data: Partial<Query<T, R>>, parent?: Query<T, R>);
|
74
113
|
any(...queries: Query<T, R>[]): Query<T, R>;
|
75
114
|
all(...queries: Query<T, R>[]): Query<T, R>;
|
@@ -78,30 +117,40 @@ export declare class Query<T, R = T> {
|
|
78
117
|
filter(constraints: FilterConstraints<T>): Query<T, R>;
|
79
118
|
filter<F extends keyof T>(column: F, value: FilterConstraints<T[F]> | DeepConstraint<T[F]>): Query<T, R>;
|
80
119
|
sort<F extends keyof T>(column: F, direction: SortDirection): Query<T, R>;
|
120
|
+
getPaginated(options?: BulkQueryOptions<T>): Promise<Page<T, R>>;
|
121
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<R>;
|
122
|
+
getIterator(chunk: number, options?: Omit<BulkQueryOptions<T>, 'page'>): AsyncGenerator<R[]>;
|
81
123
|
getMany(options?: BulkQueryOptions<T>): Promise<R[]>;
|
82
|
-
getOne(options?: BulkQueryOptions<T>): Promise<R | null>;
|
124
|
+
getOne(options?: Omit<BulkQueryOptions<T>, 'page'>): Promise<R | null>;
|
83
125
|
deleteAll(): Promise<number>;
|
84
126
|
include(columns: Include<T>): this;
|
127
|
+
nextPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
128
|
+
previousPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
129
|
+
firstPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
130
|
+
lastPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
131
|
+
hasNextPage(): boolean;
|
85
132
|
}
|
86
133
|
export declare abstract class Repository<T> extends Query<T, Selectable<T>> {
|
87
134
|
select<K extends keyof Selectable<T>>(...columns: K[]): Query<T, Select<T, K>>;
|
88
135
|
abstract create(object: Selectable<T>): Promise<T>;
|
136
|
+
abstract createMany(objects: Selectable<T>[]): Promise<T[]>;
|
89
137
|
abstract read(id: string): Promise<T | null>;
|
90
138
|
abstract update(id: string, object: Partial<T>): Promise<T>;
|
91
139
|
abstract delete(id: string): void;
|
92
|
-
abstract
|
140
|
+
abstract _runQuery<R>(query: Query<T, R>, options?: BulkQueryOptions<T>): Promise<Page<T, R>>;
|
93
141
|
}
|
94
142
|
export declare class RestRepository<T> extends Repository<T> {
|
95
143
|
client: BaseClient<any>;
|
96
144
|
fetch: any;
|
97
145
|
constructor(client: BaseClient<any>, table: string);
|
98
|
-
request(method: string, path: string, body?: unknown): Promise<
|
146
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T | undefined>;
|
99
147
|
select<K extends keyof T>(...columns: K[]): Query<T, Select<T, K>>;
|
100
148
|
create(object: T): Promise<T>;
|
149
|
+
createMany(objects: T[]): Promise<T[]>;
|
101
150
|
read(id: string): Promise<T | null>;
|
102
151
|
update(id: string, object: Partial<T>): Promise<T>;
|
103
152
|
delete(id: string): Promise<void>;
|
104
|
-
|
153
|
+
_runQuery<R>(query: Query<T, R>, options?: BulkQueryOptions<T>): Promise<Page<T, R>>;
|
105
154
|
}
|
106
155
|
interface RepositoryFactory {
|
107
156
|
createRepository<T>(client: BaseClient<any>, table: string): Repository<T>;
|
package/dist/index.js
CHANGED
@@ -15,8 +15,21 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
15
15
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
16
16
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
17
17
|
};
|
18
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
19
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
20
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
21
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
22
|
+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
23
|
+
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
24
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
25
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
26
|
+
function fulfill(value) { resume("next", value); }
|
27
|
+
function reject(value) { resume("throw", value); }
|
28
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
29
|
+
};
|
18
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
19
31
|
exports.XataError = exports.BaseClient = exports.RestRespositoryFactory = exports.RestRepository = exports.Repository = exports.Query = exports.includesAll = exports.includesPattern = exports.includesSubstring = exports.includes = exports.contains = exports.isNot = exports.is = exports.pattern = exports.endsWith = exports.startsWith = exports.notExists = exports.exists = exports.le = exports.lte = exports.lt = exports.gte = exports.ge = exports.gt = void 0;
|
32
|
+
const errors_1 = require("./util/errors");
|
20
33
|
const gt = (value) => ({ $gt: value });
|
21
34
|
exports.gt = gt;
|
22
35
|
const ge = (value) => ({ $ge: value });
|
@@ -54,8 +67,43 @@ const includesPattern = (value) => ({ $includesPattern: value });
|
|
54
67
|
exports.includesPattern = includesPattern;
|
55
68
|
const includesAll = (value) => ({ $includesAll: value });
|
56
69
|
exports.includesAll = includesAll;
|
70
|
+
class Page {
|
71
|
+
constructor(query, meta, records = []) {
|
72
|
+
this.query = query;
|
73
|
+
this.meta = meta;
|
74
|
+
this.records = records;
|
75
|
+
}
|
76
|
+
nextPage(size, offset) {
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
78
|
+
return this.query.getPaginated({ page: { size, offset, after: this.meta.page.cursor } });
|
79
|
+
});
|
80
|
+
}
|
81
|
+
previousPage(size, offset) {
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
83
|
+
return this.query.getPaginated({ page: { size, offset, before: this.meta.page.cursor } });
|
84
|
+
});
|
85
|
+
}
|
86
|
+
firstPage(size, offset) {
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
88
|
+
return this.query.getPaginated({ page: { size, offset, first: this.meta.page.cursor } });
|
89
|
+
});
|
90
|
+
}
|
91
|
+
lastPage(size, offset) {
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
93
|
+
return this.query.getPaginated({ page: { size, offset, last: this.meta.page.cursor } });
|
94
|
+
});
|
95
|
+
}
|
96
|
+
// TODO: We need to add something on the backend if we want a hasPreviousPage
|
97
|
+
hasNextPage() {
|
98
|
+
return this.meta.page.more;
|
99
|
+
}
|
100
|
+
}
|
57
101
|
class Query {
|
58
102
|
constructor(repository, table, data, parent) {
|
103
|
+
// Cursor pagination
|
104
|
+
this.query = this;
|
105
|
+
this.meta = { page: { cursor: 'start', more: true } };
|
106
|
+
this.records = [];
|
59
107
|
if (repository) {
|
60
108
|
this.repository = repository;
|
61
109
|
}
|
@@ -128,24 +176,56 @@ class Query {
|
|
128
176
|
}, this);
|
129
177
|
return q;
|
130
178
|
}
|
131
|
-
|
132
|
-
|
179
|
+
getPaginated(options) {
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
181
|
+
return this.repository._runQuery(this, options);
|
182
|
+
});
|
183
|
+
}
|
184
|
+
[Symbol.asyncIterator]() {
|
185
|
+
return __asyncGenerator(this, arguments, function* _a() {
|
186
|
+
var e_1, _b;
|
187
|
+
try {
|
188
|
+
for (var _c = __asyncValues(this.getIterator(1)), _d; _d = yield __await(_c.next()), !_d.done;) {
|
189
|
+
const [record] = _d.value;
|
190
|
+
yield yield __await(record);
|
191
|
+
}
|
192
|
+
}
|
193
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
194
|
+
finally {
|
195
|
+
try {
|
196
|
+
if (_d && !_d.done && (_b = _c.return)) yield __await(_b.call(_c));
|
197
|
+
}
|
198
|
+
finally { if (e_1) throw e_1.error; }
|
199
|
+
}
|
200
|
+
});
|
201
|
+
}
|
202
|
+
getIterator(chunk, options = {}) {
|
203
|
+
return __asyncGenerator(this, arguments, function* getIterator_1() {
|
204
|
+
let offset = 0;
|
205
|
+
let end = false;
|
206
|
+
while (!end) {
|
207
|
+
const { records, meta } = yield __await(this.getPaginated(Object.assign(Object.assign({}, options), { page: { size: chunk, offset } })));
|
208
|
+
yield yield __await(records);
|
209
|
+
offset += chunk;
|
210
|
+
end = !meta.page.more;
|
211
|
+
}
|
212
|
+
});
|
213
|
+
}
|
133
214
|
getMany(options) {
|
134
215
|
return __awaiter(this, void 0, void 0, function* () {
|
135
|
-
|
136
|
-
return
|
216
|
+
const { records } = yield this.getPaginated(options);
|
217
|
+
return records;
|
137
218
|
});
|
138
219
|
}
|
139
|
-
getOne(options) {
|
220
|
+
getOne(options = {}) {
|
140
221
|
return __awaiter(this, void 0, void 0, function* () {
|
141
|
-
|
142
|
-
|
143
|
-
return arr[0] || null;
|
222
|
+
const records = yield this.getMany(Object.assign(Object.assign({}, options), { page: { size: 1 } }));
|
223
|
+
return records[0] || null;
|
144
224
|
});
|
145
225
|
}
|
146
226
|
deleteAll() {
|
147
227
|
return __awaiter(this, void 0, void 0, function* () {
|
148
|
-
// Return number of affected rows
|
228
|
+
// TODO: Return number of affected rows
|
149
229
|
return 0;
|
150
230
|
});
|
151
231
|
}
|
@@ -153,6 +233,29 @@ class Query {
|
|
153
233
|
// TODO
|
154
234
|
return this;
|
155
235
|
}
|
236
|
+
nextPage(size, offset) {
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
238
|
+
return this.firstPage(size, offset);
|
239
|
+
});
|
240
|
+
}
|
241
|
+
previousPage(size, offset) {
|
242
|
+
return __awaiter(this, void 0, void 0, function* () {
|
243
|
+
return this.firstPage(size, offset);
|
244
|
+
});
|
245
|
+
}
|
246
|
+
firstPage(size, offset) {
|
247
|
+
return __awaiter(this, void 0, void 0, function* () {
|
248
|
+
return this.getPaginated({ page: { size, offset } });
|
249
|
+
});
|
250
|
+
}
|
251
|
+
lastPage(size, offset) {
|
252
|
+
return __awaiter(this, void 0, void 0, function* () {
|
253
|
+
return this.getPaginated({ page: { size, offset, before: 'end' } });
|
254
|
+
});
|
255
|
+
}
|
256
|
+
hasNextPage() {
|
257
|
+
return this.meta.page.more;
|
258
|
+
}
|
156
259
|
}
|
157
260
|
exports.Query = Query;
|
158
261
|
class Repository extends Query {
|
@@ -165,25 +268,16 @@ class RestRepository extends Repository {
|
|
165
268
|
constructor(client, table) {
|
166
269
|
super(null, table, {});
|
167
270
|
this.client = client;
|
168
|
-
const
|
169
|
-
|
271
|
+
const doWeHaveFetch = typeof fetch !== 'undefined';
|
272
|
+
const isInjectedFetchProblematic = !this.client.options.fetch;
|
273
|
+
if (doWeHaveFetch) {
|
170
274
|
this.fetch = fetch;
|
171
275
|
}
|
172
|
-
else if (
|
173
|
-
|
276
|
+
else if (isInjectedFetchProblematic) {
|
277
|
+
throw new Error(errors_1.errors.falsyFetchImplementation);
|
174
278
|
}
|
175
|
-
else
|
176
|
-
|
177
|
-
this.fetch = require('node-fetch');
|
178
|
-
}
|
179
|
-
catch (err) {
|
180
|
-
try {
|
181
|
-
this.fetch = require('cross-fetch');
|
182
|
-
}
|
183
|
-
catch (err) {
|
184
|
-
throw new Error('No fetch implementation found. Please provide one in the constructor');
|
185
|
-
}
|
186
|
-
}
|
279
|
+
else {
|
280
|
+
this.fetch = this.client.options.fetch;
|
187
281
|
}
|
188
282
|
Object.defineProperty(this, 'client', { enumerable: false });
|
189
283
|
Object.defineProperty(this, 'fetch', { enumerable: false });
|
@@ -219,7 +313,7 @@ class RestRepository extends Repository {
|
|
219
313
|
throw new XataError(resp.statusText, resp.status);
|
220
314
|
}
|
221
315
|
if (resp.status === 204)
|
222
|
-
return;
|
316
|
+
return undefined;
|
223
317
|
return resp.json();
|
224
318
|
});
|
225
319
|
}
|
@@ -228,22 +322,40 @@ class RestRepository extends Repository {
|
|
228
322
|
}
|
229
323
|
create(object) {
|
230
324
|
return __awaiter(this, void 0, void 0, function* () {
|
231
|
-
const
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
325
|
+
const record = transformObjectLinks(object);
|
326
|
+
const response = yield this.request('POST', `/tables/${this.table}/data`, record);
|
327
|
+
if (!response) {
|
328
|
+
throw new Error("The server didn't return any data for the query");
|
329
|
+
}
|
330
|
+
const finalObject = yield this.read(response.id);
|
331
|
+
if (!finalObject) {
|
332
|
+
throw new Error('The server failed to save the record');
|
237
333
|
}
|
238
|
-
|
239
|
-
|
334
|
+
return finalObject;
|
335
|
+
});
|
336
|
+
}
|
337
|
+
createMany(objects) {
|
338
|
+
return __awaiter(this, void 0, void 0, function* () {
|
339
|
+
const records = objects.map((object) => transformObjectLinks(object));
|
340
|
+
const response = yield this.request('POST', `/tables/${this.table}/bulk`, { records });
|
341
|
+
if (!response) {
|
342
|
+
throw new Error("The server didn't return any data for the query");
|
343
|
+
}
|
344
|
+
// TODO: Use filer.$any() to get all the records
|
345
|
+
const finalObjects = yield Promise.all(response.recordIDs.map((id) => this.read(id)));
|
346
|
+
if (finalObjects.some((object) => !object)) {
|
347
|
+
throw new Error('The server failed to save the record');
|
348
|
+
}
|
349
|
+
return finalObjects;
|
240
350
|
});
|
241
351
|
}
|
242
352
|
read(id) {
|
243
353
|
return __awaiter(this, void 0, void 0, function* () {
|
244
354
|
try {
|
245
|
-
const
|
246
|
-
|
355
|
+
const response = yield this.request('GET', `/tables/${this.table}/data/${id}`);
|
356
|
+
if (!response)
|
357
|
+
return null;
|
358
|
+
return this.client.initObject(this.table, response);
|
247
359
|
}
|
248
360
|
catch (err) {
|
249
361
|
if (err.status === 404)
|
@@ -254,8 +366,12 @@ class RestRepository extends Repository {
|
|
254
366
|
}
|
255
367
|
update(id, object) {
|
256
368
|
return __awaiter(this, void 0, void 0, function* () {
|
257
|
-
const
|
258
|
-
|
369
|
+
const response = yield this.request('PUT', `/tables/${this.table}/data/${id}`, object);
|
370
|
+
if (!response) {
|
371
|
+
throw new Error("The server didn't return any data for the query");
|
372
|
+
}
|
373
|
+
// TODO: Review this, not sure we are properly initializing the object
|
374
|
+
return this.client.initObject(this.table, response);
|
259
375
|
});
|
260
376
|
}
|
261
377
|
delete(id) {
|
@@ -263,7 +379,7 @@ class RestRepository extends Repository {
|
|
263
379
|
yield this.request('DELETE', `/tables/${this.table}/data/${id}`);
|
264
380
|
});
|
265
381
|
}
|
266
|
-
|
382
|
+
_runQuery(query, options) {
|
267
383
|
return __awaiter(this, void 0, void 0, function* () {
|
268
384
|
const filter = {
|
269
385
|
$any: query.$any,
|
@@ -273,10 +389,16 @@ class RestRepository extends Repository {
|
|
273
389
|
};
|
274
390
|
const body = {
|
275
391
|
filter: Object.values(filter).some(Boolean) ? filter : undefined,
|
276
|
-
sort: query.$sort
|
392
|
+
sort: query.$sort,
|
393
|
+
page: options === null || options === void 0 ? void 0 : options.page
|
277
394
|
};
|
278
|
-
const
|
279
|
-
|
395
|
+
const response = yield this.request('POST', `/tables/${this.table}/query`, body);
|
396
|
+
if (!response) {
|
397
|
+
throw new Error("The server didn't return any data for the query");
|
398
|
+
}
|
399
|
+
const { meta, records: objects } = response;
|
400
|
+
const records = objects.map((record) => this.client.initObject(this.table, record));
|
401
|
+
return new Page(query, meta, records);
|
280
402
|
});
|
281
403
|
}
|
282
404
|
}
|
@@ -335,7 +457,7 @@ class BaseClient {
|
|
335
457
|
return o;
|
336
458
|
}
|
337
459
|
getBranch() {
|
338
|
-
var
|
460
|
+
var e_2, _a;
|
339
461
|
return __awaiter(this, void 0, void 0, function* () {
|
340
462
|
if (this.branch)
|
341
463
|
return this.branch;
|
@@ -354,12 +476,12 @@ class BaseClient {
|
|
354
476
|
}
|
355
477
|
}
|
356
478
|
}
|
357
|
-
catch (
|
479
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
358
480
|
finally {
|
359
481
|
try {
|
360
482
|
if (strategies_1_1 && !strategies_1_1.done && (_a = strategies_1.return)) yield _a.call(strategies_1);
|
361
483
|
}
|
362
|
-
finally { if (
|
484
|
+
finally { if (e_2) throw e_2.error; }
|
363
485
|
}
|
364
486
|
throw new Error('Unable to resolve branch value');
|
365
487
|
});
|
@@ -376,3 +498,12 @@ exports.XataError = XataError;
|
|
376
498
|
const isBranchStrategyBuilder = (strategy) => {
|
377
499
|
return typeof strategy === 'function';
|
378
500
|
};
|
501
|
+
// TODO: We can find a better implementation for links
|
502
|
+
const transformObjectLinks = (object) => {
|
503
|
+
return Object.entries(object).reduce((acc, [key, value]) => {
|
504
|
+
if (value && typeof value === 'object' && typeof value.id === 'string') {
|
505
|
+
return Object.assign(Object.assign({}, acc), { [key]: value.id });
|
506
|
+
}
|
507
|
+
return Object.assign(Object.assign({}, acc), { [key]: value });
|
508
|
+
}, {});
|
509
|
+
};
|
package/dist/index.test.js
CHANGED
@@ -219,11 +219,14 @@ function expectRequest(users, expectedRequest, callback, response) {
|
|
219
219
|
users.request = request;
|
220
220
|
yield callback();
|
221
221
|
const { calls } = request.mock;
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
222
|
+
const requests = Array.isArray(expectedRequest) ? expectedRequest : [expectedRequest];
|
223
|
+
expect(calls.length).toBe(requests.length);
|
224
|
+
for (let i = 0; i < calls.length; i++) {
|
225
|
+
const [method, path, body] = calls[i];
|
226
|
+
expect(method).toBe(requests[i].method);
|
227
|
+
expect(path).toBe(requests[i].path);
|
228
|
+
expect(JSON.stringify(body)).toBe(JSON.stringify(requests[i].body));
|
229
|
+
}
|
227
230
|
});
|
228
231
|
}
|
229
232
|
describe('query', () => {
|
@@ -231,19 +234,25 @@ describe('query', () => {
|
|
231
234
|
test('simple query', () => __awaiter(void 0, void 0, void 0, function* () {
|
232
235
|
const { users } = buildClient();
|
233
236
|
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
234
|
-
expectRequest(users, expected, () => users.getMany(), {
|
237
|
+
expectRequest(users, expected, () => users.getMany(), {
|
238
|
+
records: [],
|
239
|
+
meta: { page: { cursor: '', more: false } }
|
240
|
+
});
|
235
241
|
}));
|
236
242
|
test('query with one filter', () => __awaiter(void 0, void 0, void 0, function* () {
|
237
243
|
const { users } = buildClient();
|
238
244
|
const expected = { method: 'POST', path: '/tables/users/query', body: { filter: { $all: [{ name: 'foo' }] } } };
|
239
|
-
expectRequest(users, expected, () => users.filter('name', 'foo').getMany(), {
|
245
|
+
expectRequest(users, expected, () => users.filter('name', 'foo').getMany(), {
|
246
|
+
records: [],
|
247
|
+
meta: { page: { cursor: '', more: false } }
|
248
|
+
});
|
240
249
|
}));
|
241
250
|
});
|
242
251
|
describe('getOne', () => {
|
243
252
|
test('returns a single object', () => __awaiter(void 0, void 0, void 0, function* () {
|
244
253
|
const { users } = buildClient();
|
245
|
-
const result = { records: [{ id: '1234' }] };
|
246
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
254
|
+
const result = { records: [{ id: '1234' }], meta: { page: { cursor: '', more: false } } };
|
255
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { page: { size: 1 } } };
|
247
256
|
expectRequest(users, expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
248
257
|
const first = yield users.select().getOne();
|
249
258
|
expect(first === null || first === void 0 ? void 0 : first.id).toBe(result.records[0].id);
|
@@ -251,8 +260,8 @@ describe('query', () => {
|
|
251
260
|
}));
|
252
261
|
test('returns null if no objects are returned', () => __awaiter(void 0, void 0, void 0, function* () {
|
253
262
|
const { users } = buildClient();
|
254
|
-
const result = { records: [] };
|
255
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
263
|
+
const result = { records: [], meta: { page: { cursor: '', more: false } } };
|
264
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { page: { size: 1 } } };
|
256
265
|
expectRequest(users, expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
257
266
|
const first = yield users.getOne();
|
258
267
|
expect(first).toBeNull();
|
@@ -295,7 +304,14 @@ describe('create', () => {
|
|
295
304
|
const { users } = buildClient();
|
296
305
|
const created = { id: 'rec_1234', _version: 0 };
|
297
306
|
const object = { name: 'Ada' };
|
298
|
-
const expected =
|
307
|
+
const expected = [
|
308
|
+
{ method: 'POST', path: '/tables/users/data', body: object },
|
309
|
+
{
|
310
|
+
method: 'GET',
|
311
|
+
path: '/tables/users/data/rec_1234',
|
312
|
+
body: undefined
|
313
|
+
}
|
314
|
+
];
|
299
315
|
expectRequest(users, expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
300
316
|
const result = yield users.create(object);
|
301
317
|
expect(result.id).toBe(created.id);
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.errors = void 0;
|
4
|
+
exports.errors = {
|
5
|
+
falsyFetchImplementation: `The \`fetch\` option passed to the Xata client is resolving to a falsy value and may not be correctly imported.
|
6
|
+
|
7
|
+
More in the docs:
|
8
|
+
` /** @todo add a link after docs exist */
|
9
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@xata.io/client",
|
3
|
-
"version": "0.0.0-beta.
|
3
|
+
"version": "0.0.0-beta.d2072d7",
|
4
4
|
"description": "Xata.io SDK for TypeScript and JavaScript",
|
5
5
|
"main": "./dist/index.js",
|
6
6
|
"types": "./dist/index.d.ts",
|
@@ -20,5 +20,5 @@
|
|
20
20
|
"url": "https://github.com/xataio/client-ts/issues"
|
21
21
|
},
|
22
22
|
"homepage": "https://github.com/xataio/client-ts/blob/main/client/README.md",
|
23
|
-
"gitHead": "
|
23
|
+
"gitHead": "d2072d776e9c03b22964d7413cb41dbd3456d59a"
|
24
24
|
}
|
package/src/index.test.ts
CHANGED
@@ -262,9 +262,9 @@ type ExpectedRequest = {
|
|
262
262
|
|
263
263
|
async function expectRequest(
|
264
264
|
users: RestRepository<User>,
|
265
|
-
expectedRequest: ExpectedRequest,
|
265
|
+
expectedRequest: ExpectedRequest[] | ExpectedRequest,
|
266
266
|
callback: () => void,
|
267
|
-
response?:
|
267
|
+
response?: any
|
268
268
|
) {
|
269
269
|
const request = jest.fn(async () => response);
|
270
270
|
users.request = request;
|
@@ -272,11 +272,17 @@ async function expectRequest(
|
|
272
272
|
await callback();
|
273
273
|
|
274
274
|
const { calls } = request.mock;
|
275
|
-
|
276
|
-
const
|
277
|
-
|
278
|
-
expect(
|
279
|
-
|
275
|
+
|
276
|
+
const requests = Array.isArray(expectedRequest) ? expectedRequest : [expectedRequest];
|
277
|
+
|
278
|
+
expect(calls.length).toBe(requests.length);
|
279
|
+
|
280
|
+
for (let i = 0; i < calls.length; i++) {
|
281
|
+
const [method, path, body] = calls[i] as any;
|
282
|
+
expect(method).toBe(requests[i].method);
|
283
|
+
expect(path).toBe(requests[i].path);
|
284
|
+
expect(JSON.stringify(body)).toBe(JSON.stringify(requests[i].body));
|
285
|
+
}
|
280
286
|
}
|
281
287
|
|
282
288
|
describe('query', () => {
|
@@ -285,14 +291,20 @@ describe('query', () => {
|
|
285
291
|
const { users } = buildClient();
|
286
292
|
|
287
293
|
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
288
|
-
expectRequest(users, expected, () => users.getMany(), {
|
294
|
+
expectRequest(users, expected, () => users.getMany(), {
|
295
|
+
records: [],
|
296
|
+
meta: { page: { cursor: '', more: false } }
|
297
|
+
});
|
289
298
|
});
|
290
299
|
|
291
300
|
test('query with one filter', async () => {
|
292
301
|
const { users } = buildClient();
|
293
302
|
|
294
303
|
const expected = { method: 'POST', path: '/tables/users/query', body: { filter: { $all: [{ name: 'foo' }] } } };
|
295
|
-
expectRequest(users, expected, () => users.filter('name', 'foo').getMany(), {
|
304
|
+
expectRequest(users, expected, () => users.filter('name', 'foo').getMany(), {
|
305
|
+
records: [],
|
306
|
+
meta: { page: { cursor: '', more: false } }
|
307
|
+
});
|
296
308
|
});
|
297
309
|
});
|
298
310
|
|
@@ -300,8 +312,8 @@ describe('query', () => {
|
|
300
312
|
test('returns a single object', async () => {
|
301
313
|
const { users } = buildClient();
|
302
314
|
|
303
|
-
const result = { records: [{ id: '1234' }] };
|
304
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
315
|
+
const result = { records: [{ id: '1234' }], meta: { page: { cursor: '', more: false } } };
|
316
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { page: { size: 1 } } };
|
305
317
|
expectRequest(
|
306
318
|
users,
|
307
319
|
expected,
|
@@ -316,8 +328,8 @@ describe('query', () => {
|
|
316
328
|
test('returns null if no objects are returned', async () => {
|
317
329
|
const { users } = buildClient();
|
318
330
|
|
319
|
-
const result = { records: [] };
|
320
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
331
|
+
const result = { records: [], meta: { page: { cursor: '', more: false } } };
|
332
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { page: { size: 1 } } };
|
321
333
|
expectRequest(
|
322
334
|
users,
|
323
335
|
expected,
|
@@ -378,7 +390,15 @@ describe('create', () => {
|
|
378
390
|
|
379
391
|
const created = { id: 'rec_1234', _version: 0 };
|
380
392
|
const object = { name: 'Ada' } as User;
|
381
|
-
const expected =
|
393
|
+
const expected = [
|
394
|
+
{ method: 'POST', path: '/tables/users/data', body: object },
|
395
|
+
{
|
396
|
+
method: 'GET',
|
397
|
+
path: '/tables/users/data/rec_1234',
|
398
|
+
body: undefined
|
399
|
+
}
|
400
|
+
];
|
401
|
+
|
382
402
|
expectRequest(
|
383
403
|
users,
|
384
404
|
expected,
|
package/src/index.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import { errors } from './util/errors';
|
2
|
+
|
1
3
|
export interface XataRecord {
|
2
4
|
id: string;
|
3
5
|
xata: {
|
@@ -90,7 +92,13 @@ type FilterConstraints<T> = {
|
|
90
92
|
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] | DeepConstraint<T[key]>;
|
91
93
|
};
|
92
94
|
|
95
|
+
type CursorNavigationOptions = { first?: string } | { last?: string } | { after?: string; before?: string };
|
96
|
+
type OffsetNavigationOptions = { size?: number; offset?: number };
|
97
|
+
type PaginationOptions = CursorNavigationOptions & OffsetNavigationOptions;
|
98
|
+
|
93
99
|
type BulkQueryOptions<T> = {
|
100
|
+
page?: PaginationOptions;
|
101
|
+
/** TODO: Not implemented yet
|
94
102
|
filter?: FilterConstraints<T>;
|
95
103
|
sort?:
|
96
104
|
| {
|
@@ -98,11 +106,60 @@ type BulkQueryOptions<T> = {
|
|
98
106
|
direction?: SortDirection;
|
99
107
|
}
|
100
108
|
| keyof T;
|
109
|
+
**/
|
101
110
|
};
|
102
111
|
|
103
112
|
type QueryOrConstraint<T, R> = Query<T, R> | Constraint<T>;
|
104
113
|
|
105
|
-
|
114
|
+
type QueryMeta = { page: { cursor: string; more: boolean } };
|
115
|
+
|
116
|
+
interface BasePage<T, R> {
|
117
|
+
query: Query<T, R>;
|
118
|
+
meta: QueryMeta;
|
119
|
+
records: R[];
|
120
|
+
|
121
|
+
nextPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
122
|
+
previousPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
123
|
+
firstPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
124
|
+
lastPage(size?: number, offset?: number): Promise<Page<T, R>>;
|
125
|
+
|
126
|
+
hasNextPage(): boolean;
|
127
|
+
}
|
128
|
+
|
129
|
+
class Page<T, R> implements BasePage<T, R> {
|
130
|
+
readonly query: Query<T, R>;
|
131
|
+
readonly meta: QueryMeta;
|
132
|
+
readonly records: R[];
|
133
|
+
|
134
|
+
constructor(query: Query<T, R>, meta: QueryMeta, records: R[] = []) {
|
135
|
+
this.query = query;
|
136
|
+
this.meta = meta;
|
137
|
+
this.records = records;
|
138
|
+
}
|
139
|
+
|
140
|
+
async nextPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
141
|
+
return this.query.getPaginated({ page: { size, offset, after: this.meta.page.cursor } });
|
142
|
+
}
|
143
|
+
|
144
|
+
async previousPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
145
|
+
return this.query.getPaginated({ page: { size, offset, before: this.meta.page.cursor } });
|
146
|
+
}
|
147
|
+
|
148
|
+
async firstPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
149
|
+
return this.query.getPaginated({ page: { size, offset, first: this.meta.page.cursor } });
|
150
|
+
}
|
151
|
+
|
152
|
+
async lastPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
153
|
+
return this.query.getPaginated({ page: { size, offset, last: this.meta.page.cursor } });
|
154
|
+
}
|
155
|
+
|
156
|
+
// TODO: We need to add something on the backend if we want a hasPreviousPage
|
157
|
+
hasNextPage(): boolean {
|
158
|
+
return this.meta.page.more;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
export class Query<T, R = T> implements BasePage<T, R> {
|
106
163
|
table: string;
|
107
164
|
repository: Repository<T>;
|
108
165
|
|
@@ -112,6 +169,11 @@ export class Query<T, R = T> {
|
|
112
169
|
readonly $none?: QueryOrConstraint<T, R>[];
|
113
170
|
readonly $sort?: Record<string, SortDirection>;
|
114
171
|
|
172
|
+
// Cursor pagination
|
173
|
+
readonly query: Query<T, R> = this;
|
174
|
+
readonly meta: QueryMeta = { page: { cursor: 'start', more: true } };
|
175
|
+
readonly records: R[] = [];
|
176
|
+
|
115
177
|
constructor(repository: Repository<T> | null, table: string, data: Partial<Query<T, R>>, parent?: Query<T, R>) {
|
116
178
|
if (repository) {
|
117
179
|
this.repository = repository;
|
@@ -231,21 +293,41 @@ export class Query<T, R = T> {
|
|
231
293
|
return q;
|
232
294
|
}
|
233
295
|
|
234
|
-
|
235
|
-
|
296
|
+
async getPaginated(options?: BulkQueryOptions<T>): Promise<Page<T, R>> {
|
297
|
+
return this.repository._runQuery(this, options);
|
298
|
+
}
|
299
|
+
|
300
|
+
async *[Symbol.asyncIterator](): AsyncIterableIterator<R> {
|
301
|
+
for await (const [record] of this.getIterator(1)) {
|
302
|
+
yield record;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
async *getIterator(chunk: number, options: Omit<BulkQueryOptions<T>, 'page'> = {}): AsyncGenerator<R[]> {
|
307
|
+
let offset = 0;
|
308
|
+
let end = false;
|
309
|
+
|
310
|
+
while (!end) {
|
311
|
+
const { records, meta } = await this.getPaginated({ ...options, page: { size: chunk, offset } });
|
312
|
+
yield records;
|
313
|
+
|
314
|
+
offset += chunk;
|
315
|
+
end = !meta.page.more;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
236
319
|
async getMany(options?: BulkQueryOptions<T>): Promise<R[]> {
|
237
|
-
|
238
|
-
return
|
320
|
+
const { records } = await this.getPaginated(options);
|
321
|
+
return records;
|
239
322
|
}
|
240
323
|
|
241
|
-
async getOne(options
|
242
|
-
|
243
|
-
|
244
|
-
return arr[0] || null;
|
324
|
+
async getOne(options: Omit<BulkQueryOptions<T>, 'page'> = {}): Promise<R | null> {
|
325
|
+
const records = await this.getMany({ ...options, page: { size: 1 } });
|
326
|
+
return records[0] || null;
|
245
327
|
}
|
246
328
|
|
247
329
|
async deleteAll(): Promise<number> {
|
248
|
-
// Return number of affected rows
|
330
|
+
// TODO: Return number of affected rows
|
249
331
|
return 0;
|
250
332
|
}
|
251
333
|
|
@@ -253,6 +335,26 @@ export class Query<T, R = T> {
|
|
253
335
|
// TODO
|
254
336
|
return this;
|
255
337
|
}
|
338
|
+
|
339
|
+
async nextPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
340
|
+
return this.firstPage(size, offset);
|
341
|
+
}
|
342
|
+
|
343
|
+
async previousPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
344
|
+
return this.firstPage(size, offset);
|
345
|
+
}
|
346
|
+
|
347
|
+
async firstPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
348
|
+
return this.getPaginated({ page: { size, offset } });
|
349
|
+
}
|
350
|
+
|
351
|
+
async lastPage(size?: number, offset?: number): Promise<Page<T, R>> {
|
352
|
+
return this.getPaginated({ page: { size, offset, before: 'end' } });
|
353
|
+
}
|
354
|
+
|
355
|
+
hasNextPage(): boolean {
|
356
|
+
return this.meta.page.more;
|
357
|
+
}
|
256
358
|
}
|
257
359
|
|
258
360
|
export abstract class Repository<T> extends Query<T, Selectable<T>> {
|
@@ -262,6 +364,8 @@ export abstract class Repository<T> extends Query<T, Selectable<T>> {
|
|
262
364
|
|
263
365
|
abstract create(object: Selectable<T>): Promise<T>;
|
264
366
|
|
367
|
+
abstract createMany(objects: Selectable<T>[]): Promise<T[]>;
|
368
|
+
|
265
369
|
abstract read(id: string): Promise<T | null>;
|
266
370
|
|
267
371
|
abstract update(id: string, object: Partial<T>): Promise<T>;
|
@@ -269,7 +373,7 @@ export abstract class Repository<T> extends Query<T, Selectable<T>> {
|
|
269
373
|
abstract delete(id: string): void;
|
270
374
|
|
271
375
|
// Used by the Query object internally
|
272
|
-
abstract
|
376
|
+
abstract _runQuery<R>(query: Query<T, R>, options?: BulkQueryOptions<T>): Promise<Page<T, R>>;
|
273
377
|
}
|
274
378
|
|
275
379
|
export class RestRepository<T> extends Repository<T> {
|
@@ -280,22 +384,15 @@ export class RestRepository<T> extends Repository<T> {
|
|
280
384
|
super(null, table, {});
|
281
385
|
this.client = client;
|
282
386
|
|
283
|
-
const
|
387
|
+
const doWeHaveFetch = typeof fetch !== 'undefined';
|
388
|
+
const isInjectedFetchProblematic = !this.client.options.fetch;
|
284
389
|
|
285
|
-
if (
|
390
|
+
if (doWeHaveFetch) {
|
286
391
|
this.fetch = fetch;
|
287
|
-
} else if (
|
288
|
-
|
289
|
-
} else
|
290
|
-
|
291
|
-
this.fetch = require('node-fetch');
|
292
|
-
} catch (err) {
|
293
|
-
try {
|
294
|
-
this.fetch = require('cross-fetch');
|
295
|
-
} catch (err) {
|
296
|
-
throw new Error('No fetch implementation found. Please provide one in the constructor');
|
297
|
-
}
|
298
|
-
}
|
392
|
+
} else if (isInjectedFetchProblematic) {
|
393
|
+
throw new Error(errors.falsyFetchImplementation);
|
394
|
+
} else {
|
395
|
+
this.fetch = this.client.options.fetch;
|
299
396
|
}
|
300
397
|
|
301
398
|
Object.defineProperty(this, 'client', { enumerable: false });
|
@@ -303,7 +400,7 @@ export class RestRepository<T> extends Repository<T> {
|
|
303
400
|
Object.defineProperty(this, 'hostname', { enumerable: false });
|
304
401
|
}
|
305
402
|
|
306
|
-
async request(method: string, path: string, body?: unknown) {
|
403
|
+
async request<T>(method: string, path: string, body?: unknown): Promise<T | undefined> {
|
307
404
|
const { databaseURL, apiKey } = this.client.options;
|
308
405
|
const branch = await this.client.getBranch();
|
309
406
|
|
@@ -316,6 +413,7 @@ export class RestRepository<T> extends Repository<T> {
|
|
316
413
|
},
|
317
414
|
body: JSON.stringify(body)
|
318
415
|
});
|
416
|
+
|
319
417
|
if (!resp.ok) {
|
320
418
|
try {
|
321
419
|
const json = await resp.json();
|
@@ -330,7 +428,8 @@ export class RestRepository<T> extends Repository<T> {
|
|
330
428
|
}
|
331
429
|
throw new XataError(resp.statusText, resp.status);
|
332
430
|
}
|
333
|
-
|
431
|
+
|
432
|
+
if (resp.status === 204) return undefined;
|
334
433
|
return resp.json();
|
335
434
|
}
|
336
435
|
|
@@ -339,21 +438,51 @@ export class RestRepository<T> extends Repository<T> {
|
|
339
438
|
}
|
340
439
|
|
341
440
|
async create(object: T): Promise<T> {
|
342
|
-
const
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
441
|
+
const record = transformObjectLinks(object);
|
442
|
+
|
443
|
+
const response = await this.request<{
|
444
|
+
id: string;
|
445
|
+
xata: { version: number };
|
446
|
+
}>('POST', `/tables/${this.table}/data`, record);
|
447
|
+
if (!response) {
|
448
|
+
throw new Error("The server didn't return any data for the query");
|
449
|
+
}
|
450
|
+
|
451
|
+
const finalObject = await this.read(response.id);
|
452
|
+
if (!finalObject) {
|
453
|
+
throw new Error('The server failed to save the record');
|
454
|
+
}
|
455
|
+
|
456
|
+
return finalObject;
|
457
|
+
}
|
458
|
+
|
459
|
+
async createMany(objects: T[]): Promise<T[]> {
|
460
|
+
const records = objects.map((object) => transformObjectLinks(object));
|
461
|
+
|
462
|
+
const response = await this.request<{
|
463
|
+
recordIDs: string[];
|
464
|
+
}>('POST', `/tables/${this.table}/bulk`, { records });
|
465
|
+
if (!response) {
|
466
|
+
throw new Error("The server didn't return any data for the query");
|
467
|
+
}
|
468
|
+
|
469
|
+
// TODO: Use filer.$any() to get all the records
|
470
|
+
const finalObjects = await Promise.all(response.recordIDs.map((id) => this.read(id)));
|
471
|
+
if (finalObjects.some((object) => !object)) {
|
472
|
+
throw new Error('The server failed to save the record');
|
348
473
|
}
|
349
|
-
|
350
|
-
return
|
474
|
+
|
475
|
+
return finalObjects as T[];
|
351
476
|
}
|
352
477
|
|
353
478
|
async read(id: string): Promise<T | null> {
|
354
479
|
try {
|
355
|
-
const
|
356
|
-
|
480
|
+
const response = await this.request<
|
481
|
+
T & { id: string; xata: { version: number; table?: string; warnings?: string[] } }
|
482
|
+
>('GET', `/tables/${this.table}/data/${id}`);
|
483
|
+
if (!response) return null;
|
484
|
+
|
485
|
+
return this.client.initObject(this.table, response);
|
357
486
|
} catch (err) {
|
358
487
|
if ((err as XataError).status === 404) return null;
|
359
488
|
throw err;
|
@@ -361,27 +490,48 @@ export class RestRepository<T> extends Repository<T> {
|
|
361
490
|
}
|
362
491
|
|
363
492
|
async update(id: string, object: Partial<T>): Promise<T> {
|
364
|
-
const
|
365
|
-
|
493
|
+
const response = await this.request<{
|
494
|
+
id: string;
|
495
|
+
xata: { version: number };
|
496
|
+
}>('PUT', `/tables/${this.table}/data/${id}`, object);
|
497
|
+
if (!response) {
|
498
|
+
throw new Error("The server didn't return any data for the query");
|
499
|
+
}
|
500
|
+
|
501
|
+
// TODO: Review this, not sure we are properly initializing the object
|
502
|
+
return this.client.initObject(this.table, response);
|
366
503
|
}
|
367
504
|
|
368
505
|
async delete(id: string) {
|
369
506
|
await this.request('DELETE', `/tables/${this.table}/data/${id}`);
|
370
507
|
}
|
371
508
|
|
372
|
-
async
|
509
|
+
async _runQuery<R>(query: Query<T, R>, options?: BulkQueryOptions<T>): Promise<Page<T, R>> {
|
373
510
|
const filter = {
|
374
511
|
$any: query.$any,
|
375
512
|
$all: query.$all,
|
376
513
|
$not: query.$not,
|
377
514
|
$none: query.$none
|
378
515
|
};
|
516
|
+
|
379
517
|
const body = {
|
380
518
|
filter: Object.values(filter).some(Boolean) ? filter : undefined,
|
381
|
-
sort: query.$sort
|
519
|
+
sort: query.$sort,
|
520
|
+
page: options?.page
|
382
521
|
};
|
383
|
-
|
384
|
-
|
522
|
+
|
523
|
+
const response = await this.request<{
|
524
|
+
records: object[];
|
525
|
+
meta: { page: { cursor: string; more: boolean } };
|
526
|
+
}>('POST', `/tables/${this.table}/query`, body);
|
527
|
+
if (!response) {
|
528
|
+
throw new Error("The server didn't return any data for the query");
|
529
|
+
}
|
530
|
+
|
531
|
+
const { meta, records: objects } = response;
|
532
|
+
const records = objects.map((record) => this.client.initObject<R>(this.table, record));
|
533
|
+
|
534
|
+
return new Page(query, meta, records);
|
385
535
|
}
|
386
536
|
}
|
387
537
|
|
@@ -504,3 +654,14 @@ export type Links = Record<string, Array<string[]>>;
|
|
504
654
|
const isBranchStrategyBuilder = (strategy: BranchStrategy): strategy is BranchStrategyBuilder => {
|
505
655
|
return typeof strategy === 'function';
|
506
656
|
};
|
657
|
+
|
658
|
+
// TODO: We can find a better implementation for links
|
659
|
+
const transformObjectLinks = (object: any) => {
|
660
|
+
return Object.entries(object).reduce((acc, [key, value]) => {
|
661
|
+
if (value && typeof value === 'object' && typeof (value as Record<string, unknown>).id === 'string') {
|
662
|
+
return { ...acc, [key]: (value as XataRecord).id };
|
663
|
+
}
|
664
|
+
|
665
|
+
return { ...acc, [key]: value };
|
666
|
+
}, {});
|
667
|
+
};
|