@xata.io/client 0.1.1 → 0.1.5
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/README.md +1 -0
- package/dist/index.d.ts +30 -28
- package/dist/index.js +68 -53
- package/dist/index.test.js +12 -12
- package/package.json +2 -2
- package/src/index.test.ts +12 -12
- package/src/index.ts +101 -80
package/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Visit https://github.com/xataio/client-ts for more information.
|
package/dist/index.d.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
export interface XataRecord {
|
2
|
-
|
3
|
-
|
2
|
+
id: string;
|
3
|
+
xata: {
|
4
|
+
version: number;
|
5
|
+
};
|
4
6
|
read(): Promise<this>;
|
5
7
|
update(data: Selectable<this>): Promise<this>;
|
6
8
|
delete(): Promise<void>;
|
@@ -17,31 +19,40 @@ export declare type OmitLinks<T> = {
|
|
17
19
|
export declare type OmitMethods<T> = {
|
18
20
|
[key in keyof T as T[key] extends Function ? never : key]: T[key];
|
19
21
|
};
|
20
|
-
export declare type Selectable<T> = Omit<OmitQueries<OmitMethods<T>>, '
|
22
|
+
export declare type Selectable<T> = Omit<OmitQueries<OmitMethods<T>>, 'id' | 'xata'>;
|
21
23
|
export declare type Select<T, K extends keyof T> = Pick<T, K> & Queries<T> & XataRecord;
|
22
24
|
export declare type Include<T> = {
|
23
25
|
[key in keyof T as T[key] extends XataRecord ? key : never]?: boolean | Array<keyof Selectable<T[key]>>;
|
24
26
|
};
|
25
27
|
declare type SortDirection = 'asc' | 'desc';
|
26
|
-
declare type Operator = '
|
27
|
-
declare type Constraint<T> =
|
28
|
+
declare type Operator = '$gt' | '$lt' | '$ge' | '$le' | '$exists' | '$notExists' | '$endsWith' | '$startsWith' | '$pattern' | '$is' | '$isNot' | '$contains' | '$includes' | '$includesSubstring' | '$includesPattern' | '$includesAll';
|
29
|
+
declare type Constraint<T> = {
|
30
|
+
[key in Operator]?: T;
|
31
|
+
};
|
32
|
+
declare type DeepConstraint<T> = T extends Record<string, any> ? {
|
33
|
+
[key in keyof T]?: T[key] | DeepConstraint<T[key]>;
|
34
|
+
} : Constraint<T>;
|
28
35
|
declare type ComparableType = number | Date;
|
29
|
-
export declare const gt: <T extends ComparableType>(value: T) =>
|
30
|
-
export declare const
|
31
|
-
export declare const
|
32
|
-
export declare const
|
36
|
+
export declare const gt: <T extends ComparableType>(value: T) => Constraint<T>;
|
37
|
+
export declare const ge: <T extends ComparableType>(value: T) => Constraint<T>;
|
38
|
+
export declare const gte: <T extends ComparableType>(value: T) => Constraint<T>;
|
39
|
+
export declare const lt: <T extends ComparableType>(value: T) => Constraint<T>;
|
40
|
+
export declare const lte: <T extends ComparableType>(value: T) => Constraint<T>;
|
41
|
+
export declare const le: <T extends ComparableType>(value: T) => Constraint<T>;
|
33
42
|
export declare const exists: (column: string) => Constraint<string>;
|
34
43
|
export declare const notExists: (column: string) => Constraint<string>;
|
35
44
|
export declare const startsWith: (value: string) => Constraint<string>;
|
36
45
|
export declare const endsWith: (value: string) => Constraint<string>;
|
37
46
|
export declare const pattern: (value: string) => Constraint<string>;
|
38
|
-
export declare const
|
47
|
+
export declare const is: <T>(value: T) => Constraint<T>;
|
48
|
+
export declare const isNot: <T>(value: T) => Constraint<T>;
|
49
|
+
export declare const contains: <T>(value: T) => Constraint<T>;
|
39
50
|
export declare const includes: (value: string) => Constraint<string>;
|
40
51
|
export declare const includesSubstring: (value: string) => Constraint<string>;
|
41
52
|
export declare const includesPattern: (value: string) => Constraint<string>;
|
42
53
|
export declare const includesAll: (value: string) => Constraint<string>;
|
43
54
|
declare type FilterConstraints<T> = {
|
44
|
-
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] |
|
55
|
+
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] | DeepConstraint<T[key]>;
|
45
56
|
};
|
46
57
|
declare type BulkQueryOptions<T> = {
|
47
58
|
filter?: FilterConstraints<T>;
|
@@ -54,32 +65,23 @@ declare type QueryOrConstraint<T, R> = Query<T, R> | Constraint<T>;
|
|
54
65
|
export declare class Query<T, R = T> {
|
55
66
|
table: string;
|
56
67
|
repository: Repository<T>;
|
57
|
-
readonly
|
58
|
-
readonly
|
59
|
-
readonly
|
60
|
-
readonly
|
61
|
-
readonly
|
68
|
+
readonly $any?: QueryOrConstraint<T, R>[];
|
69
|
+
readonly $all?: QueryOrConstraint<T, R>[];
|
70
|
+
readonly $not?: QueryOrConstraint<T, R>[];
|
71
|
+
readonly $none?: QueryOrConstraint<T, R>[];
|
72
|
+
readonly $sort?: Record<string, SortDirection>;
|
62
73
|
constructor(repository: Repository<T> | null, table: string, data: Partial<Query<T, R>>, parent?: Query<T, R>);
|
63
74
|
any(...queries: Query<T, R>[]): Query<T, R>;
|
64
75
|
all(...queries: Query<T, R>[]): Query<T, R>;
|
65
76
|
not(...queries: Query<T, R>[]): Query<T, R>;
|
66
77
|
none(...queries: Query<T, R>[]): Query<T, R>;
|
67
78
|
filter(constraints: FilterConstraints<T>): Query<T, R>;
|
68
|
-
filter<F extends keyof T>(column: F, value: FilterConstraints<T[F]> |
|
79
|
+
filter<F extends keyof T>(column: F, value: FilterConstraints<T[F]> | DeepConstraint<T[F]>): Query<T, R>;
|
69
80
|
sort<F extends keyof T>(column: F, direction: SortDirection): Query<T, R>;
|
70
81
|
getMany(options?: BulkQueryOptions<T>): Promise<R[]>;
|
71
82
|
getOne(options?: BulkQueryOptions<T>): Promise<R | null>;
|
72
83
|
deleteAll(): Promise<number>;
|
73
84
|
include(columns: Include<T>): this;
|
74
|
-
toJSON(): {
|
75
|
-
_filter: {
|
76
|
-
_any: QueryOrConstraint<T, R>[] | undefined;
|
77
|
-
_all: QueryOrConstraint<T, R>[] | undefined;
|
78
|
-
_not: QueryOrConstraint<T, R>[] | undefined;
|
79
|
-
_none: QueryOrConstraint<T, R>[] | undefined;
|
80
|
-
} | undefined;
|
81
|
-
_sort: Record<string, SortDirection> | undefined;
|
82
|
-
};
|
83
85
|
}
|
84
86
|
export declare abstract class Repository<T> extends Query<T, Selectable<T>> {
|
85
87
|
select<K extends keyof Selectable<T>>(...columns: K[]): Query<T, Select<T, K>>;
|
@@ -109,8 +111,8 @@ export declare class RestRespositoryFactory implements RepositoryFactory {
|
|
109
111
|
}
|
110
112
|
export declare type XataClientOptions = {
|
111
113
|
fetch?: unknown;
|
112
|
-
|
113
|
-
|
114
|
+
databaseURL: string;
|
115
|
+
apiKey: string;
|
114
116
|
repositoryFactory?: RepositoryFactory;
|
115
117
|
};
|
116
118
|
export declare class BaseClient<D extends Record<string, Repository<any>>> {
|
package/dist/index.js
CHANGED
@@ -9,35 +9,43 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
9
9
|
});
|
10
10
|
};
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
-
exports.XataError = exports.BaseClient = exports.RestRespositoryFactory = exports.RestRepository = exports.Repository = exports.Query = exports.includesAll = exports.includesPattern = exports.includesSubstring = exports.includes = exports.isNot = exports.pattern = exports.endsWith = exports.startsWith = exports.notExists = exports.exists = exports.lte = exports.lt = exports.gte = exports.gt = void 0;
|
13
|
-
const gt = (value) => ({
|
12
|
+
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;
|
13
|
+
const gt = (value) => ({ $gt: value });
|
14
14
|
exports.gt = gt;
|
15
|
-
const
|
15
|
+
const ge = (value) => ({ $ge: value });
|
16
|
+
exports.ge = ge;
|
17
|
+
const gte = (value) => ({ $ge: value });
|
16
18
|
exports.gte = gte;
|
17
|
-
const lt = (value) => ({
|
19
|
+
const lt = (value) => ({ $lt: value });
|
18
20
|
exports.lt = lt;
|
19
|
-
const lte = (value) => ({
|
21
|
+
const lte = (value) => ({ $le: value });
|
20
22
|
exports.lte = lte;
|
21
|
-
const
|
23
|
+
const le = (value) => ({ $le: value });
|
24
|
+
exports.le = le;
|
25
|
+
const exists = (column) => ({ $exists: column });
|
22
26
|
exports.exists = exists;
|
23
|
-
const notExists = (column) => ({
|
27
|
+
const notExists = (column) => ({ $notExists: column });
|
24
28
|
exports.notExists = notExists;
|
25
|
-
const startsWith = (value) => ({
|
29
|
+
const startsWith = (value) => ({ $startsWith: value });
|
26
30
|
exports.startsWith = startsWith;
|
27
|
-
const endsWith = (value) => ({
|
31
|
+
const endsWith = (value) => ({ $endsWith: value });
|
28
32
|
exports.endsWith = endsWith;
|
29
|
-
const pattern = (value) => ({
|
33
|
+
const pattern = (value) => ({ $pattern: value });
|
30
34
|
exports.pattern = pattern;
|
31
|
-
const
|
35
|
+
const is = (value) => ({ $is: value });
|
36
|
+
exports.is = is;
|
37
|
+
const isNot = (value) => ({ $isNot: value });
|
32
38
|
exports.isNot = isNot;
|
39
|
+
const contains = (value) => ({ $contains: value });
|
40
|
+
exports.contains = contains;
|
33
41
|
// TODO: these can only be applied to columns of type "multiple"
|
34
|
-
const includes = (value) => ({
|
42
|
+
const includes = (value) => ({ $includes: value });
|
35
43
|
exports.includes = includes;
|
36
|
-
const includesSubstring = (value) => ({
|
44
|
+
const includesSubstring = (value) => ({ $includesSubstring: value });
|
37
45
|
exports.includesSubstring = includesSubstring;
|
38
|
-
const includesPattern = (value) => ({
|
46
|
+
const includesPattern = (value) => ({ $includesPattern: value });
|
39
47
|
exports.includesPattern = includesPattern;
|
40
|
-
const includesAll = (value) => ({
|
48
|
+
const includesAll = (value) => ({ $includesAll: value });
|
41
49
|
exports.includesAll = includesAll;
|
42
50
|
class Query {
|
43
51
|
constructor(repository, table, data, parent) {
|
@@ -50,11 +58,11 @@ class Query {
|
|
50
58
|
this.table = table;
|
51
59
|
// For some reason Object.assign(this, parent) didn't work in this case
|
52
60
|
// so doing all this manually:
|
53
|
-
this
|
54
|
-
this
|
55
|
-
this
|
56
|
-
this
|
57
|
-
this
|
61
|
+
this.$any = parent === null || parent === void 0 ? void 0 : parent.$any;
|
62
|
+
this.$all = parent === null || parent === void 0 ? void 0 : parent.$all;
|
63
|
+
this.$not = parent === null || parent === void 0 ? void 0 : parent.$not;
|
64
|
+
this.$none = parent === null || parent === void 0 ? void 0 : parent.$none;
|
65
|
+
this.$sort = parent === null || parent === void 0 ? void 0 : parent.$sort;
|
58
66
|
Object.assign(this, data);
|
59
67
|
// These bindings are used to support deconstructing
|
60
68
|
// const { any, not, filter, sort } = xata.users.query()
|
@@ -64,25 +72,27 @@ class Query {
|
|
64
72
|
this.filter = this.filter.bind(this);
|
65
73
|
this.sort = this.sort.bind(this);
|
66
74
|
this.none = this.none.bind(this);
|
75
|
+
Object.defineProperty(this, 'table', { enumerable: false });
|
76
|
+
Object.defineProperty(this, 'repository', { enumerable: false });
|
67
77
|
}
|
68
78
|
any(...queries) {
|
69
79
|
return new Query(this.repository, this.table, {
|
70
|
-
|
80
|
+
$any: (this.$any || []).concat(queries)
|
71
81
|
}, this);
|
72
82
|
}
|
73
83
|
all(...queries) {
|
74
84
|
return new Query(this.repository, this.table, {
|
75
|
-
|
85
|
+
$all: (this.$all || []).concat(queries)
|
76
86
|
}, this);
|
77
87
|
}
|
78
88
|
not(...queries) {
|
79
89
|
return new Query(this.repository, this.table, {
|
80
|
-
|
90
|
+
$not: (this.$not || []).concat(queries)
|
81
91
|
}, this);
|
82
92
|
}
|
83
93
|
none(...queries) {
|
84
94
|
return new Query(this.repository, this.table, {
|
85
|
-
|
95
|
+
$none: (this.$none || []).concat(queries)
|
86
96
|
}, this);
|
87
97
|
}
|
88
98
|
filter(a, b) {
|
@@ -93,21 +103,21 @@ class Query {
|
|
93
103
|
queries.push({ [column]: constraint });
|
94
104
|
}
|
95
105
|
return new Query(this.repository, this.table, {
|
96
|
-
|
106
|
+
$all: (this.$all || []).concat(queries)
|
97
107
|
}, this);
|
98
108
|
}
|
99
109
|
else {
|
100
110
|
const column = a;
|
101
111
|
const value = b;
|
102
112
|
return new Query(this.repository, this.table, {
|
103
|
-
|
113
|
+
$all: (this.$all || []).concat({ [column]: value })
|
104
114
|
}, this);
|
105
115
|
}
|
106
116
|
}
|
107
117
|
sort(column, direction) {
|
108
|
-
const sort = Object.assign(Object.assign({}, this
|
118
|
+
const sort = Object.assign(Object.assign({}, this.$sort), { [column]: direction });
|
109
119
|
const q = new Query(this.repository, this.table, {
|
110
|
-
|
120
|
+
$sort: sort
|
111
121
|
}, this);
|
112
122
|
return q;
|
113
123
|
}
|
@@ -136,18 +146,6 @@ class Query {
|
|
136
146
|
// TODO
|
137
147
|
return this;
|
138
148
|
}
|
139
|
-
toJSON() {
|
140
|
-
const _filter = {
|
141
|
-
_any: this._any,
|
142
|
-
_all: this._all,
|
143
|
-
_not: this._not,
|
144
|
-
_none: this._none
|
145
|
-
};
|
146
|
-
return {
|
147
|
-
_filter: Object.values(_filter).some(Boolean) ? _filter : undefined,
|
148
|
-
_sort: this._sort
|
149
|
-
};
|
150
|
-
}
|
151
149
|
}
|
152
150
|
exports.Query = Query;
|
153
151
|
class Repository extends Query {
|
@@ -186,13 +184,13 @@ class RestRepository extends Repository {
|
|
186
184
|
}
|
187
185
|
request(method, path, body) {
|
188
186
|
return __awaiter(this, void 0, void 0, function* () {
|
189
|
-
const {
|
190
|
-
const resp = yield this.fetch(`${
|
187
|
+
const { databaseURL } = this.client.options;
|
188
|
+
const resp = yield this.fetch(`${databaseURL}${path}`, {
|
191
189
|
method,
|
192
190
|
headers: {
|
193
191
|
Accept: '*/*',
|
194
192
|
'Content-Type': 'application/json',
|
195
|
-
Authorization: `Bearer ${this.client.options.
|
193
|
+
Authorization: `Bearer ${this.client.options.apiKey}`
|
196
194
|
},
|
197
195
|
body: JSON.stringify(body)
|
198
196
|
});
|
@@ -222,7 +220,14 @@ class RestRepository extends Repository {
|
|
222
220
|
}
|
223
221
|
create(object) {
|
224
222
|
return __awaiter(this, void 0, void 0, function* () {
|
225
|
-
const
|
223
|
+
const body = Object.assign({}, object);
|
224
|
+
for (const key of Object.keys(body)) {
|
225
|
+
const value = body[key];
|
226
|
+
if (value && typeof value === 'object' && typeof value.id === 'string') {
|
227
|
+
body[key] = value.id;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
const obj = yield this.request('POST', `/tables/${this.table}/data`, body);
|
226
231
|
return this.client.initObject(this.table, obj);
|
227
232
|
});
|
228
233
|
}
|
@@ -252,7 +257,17 @@ class RestRepository extends Repository {
|
|
252
257
|
}
|
253
258
|
query(query) {
|
254
259
|
return __awaiter(this, void 0, void 0, function* () {
|
255
|
-
const
|
260
|
+
const filter = {
|
261
|
+
$any: query.$any,
|
262
|
+
$all: query.$all,
|
263
|
+
$not: query.$not,
|
264
|
+
$none: query.$none
|
265
|
+
};
|
266
|
+
const body = {
|
267
|
+
filter: Object.values(filter).some(Boolean) ? filter : undefined,
|
268
|
+
sort: query.$sort
|
269
|
+
};
|
270
|
+
const result = yield this.request('POST', `/tables/${this.table}/query`, body);
|
256
271
|
return result.records.map((record) => this.client.initObject(this.table, record));
|
257
272
|
});
|
258
273
|
}
|
@@ -277,15 +292,15 @@ class BaseClient {
|
|
277
292
|
const [field, linkTable] = link;
|
278
293
|
const value = o[field];
|
279
294
|
if (value && typeof value === 'object') {
|
280
|
-
const {
|
281
|
-
if (Object.keys(value).find((col) =>
|
295
|
+
const { id } = value;
|
296
|
+
if (Object.keys(value).find((col) => col === 'id')) {
|
282
297
|
o[field] = this.initObject(linkTable, value);
|
283
298
|
}
|
284
|
-
else if (
|
299
|
+
else if (id) {
|
285
300
|
o[field] = {
|
286
|
-
|
301
|
+
id,
|
287
302
|
get: () => {
|
288
|
-
this.db[linkTable].read(
|
303
|
+
this.db[linkTable].read(id);
|
289
304
|
}
|
290
305
|
};
|
291
306
|
}
|
@@ -293,13 +308,13 @@ class BaseClient {
|
|
293
308
|
}
|
294
309
|
const db = this.db;
|
295
310
|
o.read = function () {
|
296
|
-
return db[table].read(o['
|
311
|
+
return db[table].read(o['id']);
|
297
312
|
};
|
298
313
|
o.update = function (data) {
|
299
|
-
return db[table].update(o['
|
314
|
+
return db[table].update(o['id'], data);
|
300
315
|
};
|
301
316
|
o.delete = function () {
|
302
|
-
return db[table].delete(o['
|
317
|
+
return db[table].delete(o['id']);
|
303
318
|
};
|
304
319
|
for (const prop of ['read', 'update', 'delete']) {
|
305
320
|
Object.defineProperty(o, prop, { enumerable: false });
|
package/dist/index.test.js
CHANGED
@@ -13,8 +13,8 @@ const _1 = require("./");
|
|
13
13
|
const fetch = jest.fn();
|
14
14
|
const client = new _1.BaseClient({
|
15
15
|
fetch,
|
16
|
-
|
17
|
-
|
16
|
+
apiKey: '1234',
|
17
|
+
databaseURL: 'https://my-workspace-5df34do.staging.xatabase.co/db/xata:main'
|
18
18
|
}, {});
|
19
19
|
const users = new _1.RestRepository(client, 'users');
|
20
20
|
describe('request', () => {
|
@@ -119,17 +119,17 @@ describe('query', () => {
|
|
119
119
|
expectRequest(expected, () => users.getMany(), { records: [] });
|
120
120
|
}));
|
121
121
|
test('query with one filter', () => __awaiter(void 0, void 0, void 0, function* () {
|
122
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {
|
122
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { filter: { $all: [{ name: 'foo' }] } } };
|
123
123
|
expectRequest(expected, () => users.filter('name', 'foo').getMany(), { records: [] });
|
124
124
|
}));
|
125
125
|
});
|
126
126
|
describe('getOne', () => {
|
127
127
|
test('returns a single object', () => __awaiter(void 0, void 0, void 0, function* () {
|
128
|
-
const result = { records: [{
|
128
|
+
const result = { records: [{ id: '1234' }] };
|
129
129
|
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
130
130
|
expectRequest(expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
131
131
|
const first = yield users.select().getOne();
|
132
|
-
expect(first === null || first === void 0 ? void 0 : first.
|
132
|
+
expect(first === null || first === void 0 ? void 0 : first.id).toBe(result.records[0].id);
|
133
133
|
}), result);
|
134
134
|
}));
|
135
135
|
test('returns null if no objects are returned', () => __awaiter(void 0, void 0, void 0, function* () {
|
@@ -151,12 +151,12 @@ describe('read', () => {
|
|
151
151
|
});
|
152
152
|
describe('Repository.update', () => {
|
153
153
|
test('updates and object successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
154
|
-
const object = {
|
155
|
-
const expected = { method: 'PUT', path: `/tables/users/data/${object.
|
154
|
+
const object = { id: 'rec_1234', xata: { version: 1 }, name: 'Ada' };
|
155
|
+
const expected = { method: 'PUT', path: `/tables/users/data/${object.id}`, body: object };
|
156
156
|
expectRequest(expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
157
|
-
const result = yield users.update(object.
|
158
|
-
expect(result.
|
159
|
-
}), {
|
157
|
+
const result = yield users.update(object.id, object);
|
158
|
+
expect(result.id).toBe(object.id);
|
159
|
+
}), { id: object.id });
|
160
160
|
}));
|
161
161
|
});
|
162
162
|
describe('Repository.delete', () => {
|
@@ -171,12 +171,12 @@ describe('Repository.delete', () => {
|
|
171
171
|
});
|
172
172
|
describe('create', () => {
|
173
173
|
test('successful', () => __awaiter(void 0, void 0, void 0, function* () {
|
174
|
-
const created = {
|
174
|
+
const created = { id: 'rec_1234', _version: 0 };
|
175
175
|
const object = { name: 'Ada' };
|
176
176
|
const expected = { method: 'POST', path: '/tables/users/data', body: object };
|
177
177
|
expectRequest(expected, () => __awaiter(void 0, void 0, void 0, function* () {
|
178
178
|
const result = yield users.create(object);
|
179
|
-
expect(result.
|
179
|
+
expect(result.id).toBe(created.id);
|
180
180
|
}), created);
|
181
181
|
}));
|
182
182
|
});
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@xata.io/client",
|
3
|
-
"version": "0.1.
|
4
|
-
"description": "Xata.io SDK for
|
3
|
+
"version": "0.1.5",
|
4
|
+
"description": "Xata.io SDK for TypeScript and JavaScript",
|
5
5
|
"main": "./dist/index.js",
|
6
6
|
"types": "./dist/index.d.ts",
|
7
7
|
"scripts": {
|
package/src/index.test.ts
CHANGED
@@ -4,8 +4,8 @@ const fetch = jest.fn();
|
|
4
4
|
const client = new BaseClient(
|
5
5
|
{
|
6
6
|
fetch,
|
7
|
-
|
8
|
-
|
7
|
+
apiKey: '1234',
|
8
|
+
databaseURL: 'https://my-workspace-5df34do.staging.xatabase.co/db/xata:main'
|
9
9
|
},
|
10
10
|
{}
|
11
11
|
);
|
@@ -138,20 +138,20 @@ describe('query', () => {
|
|
138
138
|
});
|
139
139
|
|
140
140
|
test('query with one filter', async () => {
|
141
|
-
const expected = { method: 'POST', path: '/tables/users/query', body: {
|
141
|
+
const expected = { method: 'POST', path: '/tables/users/query', body: { filter: { $all: [{ name: 'foo' }] } } };
|
142
142
|
expectRequest(expected, () => users.filter('name', 'foo').getMany(), { records: [] });
|
143
143
|
});
|
144
144
|
});
|
145
145
|
|
146
146
|
describe('getOne', () => {
|
147
147
|
test('returns a single object', async () => {
|
148
|
-
const result = { records: [{
|
148
|
+
const result = { records: [{ id: '1234' }] };
|
149
149
|
const expected = { method: 'POST', path: '/tables/users/query', body: {} };
|
150
150
|
expectRequest(
|
151
151
|
expected,
|
152
152
|
async () => {
|
153
153
|
const first = await users.select().getOne();
|
154
|
-
expect(first?.
|
154
|
+
expect(first?.id).toBe(result.records[0].id);
|
155
155
|
},
|
156
156
|
result
|
157
157
|
);
|
@@ -182,15 +182,15 @@ describe('read', () => {
|
|
182
182
|
|
183
183
|
describe('Repository.update', () => {
|
184
184
|
test('updates and object successfully', async () => {
|
185
|
-
const object = {
|
186
|
-
const expected = { method: 'PUT', path: `/tables/users/data/${object.
|
185
|
+
const object = { id: 'rec_1234', xata: { version: 1 }, name: 'Ada' } as User;
|
186
|
+
const expected = { method: 'PUT', path: `/tables/users/data/${object.id}`, body: object };
|
187
187
|
expectRequest(
|
188
188
|
expected,
|
189
189
|
async () => {
|
190
|
-
const result = await users.update(object.
|
191
|
-
expect(result.
|
190
|
+
const result = await users.update(object.id, object);
|
191
|
+
expect(result.id).toBe(object.id);
|
192
192
|
},
|
193
|
-
{
|
193
|
+
{ id: object.id }
|
194
194
|
);
|
195
195
|
});
|
196
196
|
});
|
@@ -207,14 +207,14 @@ describe('Repository.delete', () => {
|
|
207
207
|
|
208
208
|
describe('create', () => {
|
209
209
|
test('successful', async () => {
|
210
|
-
const created = {
|
210
|
+
const created = { id: 'rec_1234', _version: 0 };
|
211
211
|
const object = { name: 'Ada' } as User;
|
212
212
|
const expected = { method: 'POST', path: '/tables/users/data', body: object };
|
213
213
|
expectRequest(
|
214
214
|
expected,
|
215
215
|
async () => {
|
216
216
|
const result = await users.create(object);
|
217
|
-
expect(result.
|
217
|
+
expect(result.id).toBe(created.id);
|
218
218
|
},
|
219
219
|
created
|
220
220
|
);
|
package/src/index.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
export interface XataRecord {
|
2
|
-
|
3
|
-
|
2
|
+
id: string;
|
3
|
+
xata: {
|
4
|
+
version: number;
|
5
|
+
};
|
4
6
|
read(): Promise<this>;
|
5
7
|
update(data: Selectable<this>): Promise<this>;
|
6
8
|
delete(): Promise<void>;
|
@@ -23,7 +25,7 @@ export type OmitMethods<T> = {
|
|
23
25
|
[key in keyof T as T[key] extends Function ? never : key]: T[key];
|
24
26
|
};
|
25
27
|
|
26
|
-
export type Selectable<T> = Omit<OmitQueries<OmitMethods<T>>, '
|
28
|
+
export type Selectable<T> = Omit<OmitQueries<OmitMethods<T>>, 'id' | 'xata'>;
|
27
29
|
|
28
30
|
export type Select<T, K extends keyof T> = Pick<T, K> & Queries<T> & XataRecord;
|
29
31
|
|
@@ -34,46 +36,58 @@ export type Include<T> = {
|
|
34
36
|
type SortDirection = 'asc' | 'desc';
|
35
37
|
|
36
38
|
type Operator =
|
37
|
-
| '
|
38
|
-
| '
|
39
|
-
| '
|
40
|
-
| '
|
41
|
-
| '
|
42
|
-
| '
|
43
|
-
| '
|
44
|
-
| '
|
45
|
-
| '
|
46
|
-
| '
|
47
|
-
| '
|
48
|
-
| '
|
49
|
-
| '
|
50
|
-
| '
|
39
|
+
| '$gt'
|
40
|
+
| '$lt'
|
41
|
+
| '$ge'
|
42
|
+
| '$le'
|
43
|
+
| '$exists'
|
44
|
+
| '$notExists'
|
45
|
+
| '$endsWith'
|
46
|
+
| '$startsWith'
|
47
|
+
| '$pattern'
|
48
|
+
| '$is'
|
49
|
+
| '$isNot'
|
50
|
+
| '$contains'
|
51
|
+
| '$includes'
|
52
|
+
| '$includesSubstring'
|
53
|
+
| '$includesPattern'
|
54
|
+
| '$includesAll';
|
51
55
|
|
52
56
|
// TODO: restrict constraints depending on type?
|
53
57
|
// E.g. startsWith cannot be used with numbers
|
54
|
-
type Constraint<T> =
|
58
|
+
type Constraint<T> = { [key in Operator]?: T };
|
59
|
+
|
60
|
+
type DeepConstraint<T> = T extends Record<string, any>
|
61
|
+
? {
|
62
|
+
[key in keyof T]?: T[key] | DeepConstraint<T[key]>;
|
63
|
+
}
|
64
|
+
: Constraint<T>;
|
55
65
|
|
56
66
|
type ComparableType = number | Date;
|
57
67
|
|
58
|
-
export const gt = <T extends ComparableType>(value: T): Constraint<T> => ({
|
59
|
-
export const
|
60
|
-
export const
|
61
|
-
export const
|
62
|
-
export const
|
63
|
-
export const
|
64
|
-
export const
|
65
|
-
export const
|
66
|
-
export const
|
67
|
-
export const
|
68
|
+
export const gt = <T extends ComparableType>(value: T): Constraint<T> => ({ $gt: value });
|
69
|
+
export const ge = <T extends ComparableType>(value: T): Constraint<T> => ({ $ge: value });
|
70
|
+
export const gte = <T extends ComparableType>(value: T): Constraint<T> => ({ $ge: value });
|
71
|
+
export const lt = <T extends ComparableType>(value: T): Constraint<T> => ({ $lt: value });
|
72
|
+
export const lte = <T extends ComparableType>(value: T): Constraint<T> => ({ $le: value });
|
73
|
+
export const le = <T extends ComparableType>(value: T): Constraint<T> => ({ $le: value });
|
74
|
+
export const exists = (column: string): Constraint<string> => ({ $exists: column });
|
75
|
+
export const notExists = (column: string): Constraint<string> => ({ $notExists: column });
|
76
|
+
export const startsWith = (value: string): Constraint<string> => ({ $startsWith: value });
|
77
|
+
export const endsWith = (value: string): Constraint<string> => ({ $endsWith: value });
|
78
|
+
export const pattern = (value: string): Constraint<string> => ({ $pattern: value });
|
79
|
+
export const is = <T>(value: T): Constraint<T> => ({ $is: value });
|
80
|
+
export const isNot = <T>(value: T): Constraint<T> => ({ $isNot: value });
|
81
|
+
export const contains = <T>(value: T): Constraint<T> => ({ $contains: value });
|
68
82
|
|
69
83
|
// TODO: these can only be applied to columns of type "multiple"
|
70
|
-
export const includes = (value: string): Constraint<string> => ({
|
71
|
-
export const includesSubstring = (value: string): Constraint<string> => ({
|
72
|
-
export const includesPattern = (value: string): Constraint<string> => ({
|
73
|
-
export const includesAll = (value: string): Constraint<string> => ({
|
84
|
+
export const includes = (value: string): Constraint<string> => ({ $includes: value });
|
85
|
+
export const includesSubstring = (value: string): Constraint<string> => ({ $includesSubstring: value });
|
86
|
+
export const includesPattern = (value: string): Constraint<string> => ({ $includesPattern: value });
|
87
|
+
export const includesAll = (value: string): Constraint<string> => ({ $includesAll: value });
|
74
88
|
|
75
89
|
type FilterConstraints<T> = {
|
76
|
-
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] |
|
90
|
+
[key in keyof T]?: T[key] extends Record<string, any> ? FilterConstraints<T[key]> : T[key] | DeepConstraint<T[key]>;
|
77
91
|
};
|
78
92
|
|
79
93
|
type BulkQueryOptions<T> = {
|
@@ -92,11 +106,11 @@ export class Query<T, R = T> {
|
|
92
106
|
table: string;
|
93
107
|
repository: Repository<T>;
|
94
108
|
|
95
|
-
readonly
|
96
|
-
readonly
|
97
|
-
readonly
|
98
|
-
readonly
|
99
|
-
readonly
|
109
|
+
readonly $any?: QueryOrConstraint<T, R>[];
|
110
|
+
readonly $all?: QueryOrConstraint<T, R>[];
|
111
|
+
readonly $not?: QueryOrConstraint<T, R>[];
|
112
|
+
readonly $none?: QueryOrConstraint<T, R>[];
|
113
|
+
readonly $sort?: Record<string, SortDirection>;
|
100
114
|
|
101
115
|
constructor(repository: Repository<T> | null, table: string, data: Partial<Query<T, R>>, parent?: Query<T, R>) {
|
102
116
|
if (repository) {
|
@@ -108,11 +122,11 @@ export class Query<T, R = T> {
|
|
108
122
|
|
109
123
|
// For some reason Object.assign(this, parent) didn't work in this case
|
110
124
|
// so doing all this manually:
|
111
|
-
this
|
112
|
-
this
|
113
|
-
this
|
114
|
-
this
|
115
|
-
this
|
125
|
+
this.$any = parent?.$any;
|
126
|
+
this.$all = parent?.$all;
|
127
|
+
this.$not = parent?.$not;
|
128
|
+
this.$none = parent?.$none;
|
129
|
+
this.$sort = parent?.$sort;
|
116
130
|
|
117
131
|
Object.assign(this, data);
|
118
132
|
// These bindings are used to support deconstructing
|
@@ -123,6 +137,9 @@ export class Query<T, R = T> {
|
|
123
137
|
this.filter = this.filter.bind(this);
|
124
138
|
this.sort = this.sort.bind(this);
|
125
139
|
this.none = this.none.bind(this);
|
140
|
+
|
141
|
+
Object.defineProperty(this, 'table', { enumerable: false });
|
142
|
+
Object.defineProperty(this, 'repository', { enumerable: false });
|
126
143
|
}
|
127
144
|
|
128
145
|
any(...queries: Query<T, R>[]): Query<T, R> {
|
@@ -130,7 +147,7 @@ export class Query<T, R = T> {
|
|
130
147
|
this.repository,
|
131
148
|
this.table,
|
132
149
|
{
|
133
|
-
|
150
|
+
$any: (this.$any || []).concat(queries)
|
134
151
|
},
|
135
152
|
this
|
136
153
|
);
|
@@ -141,7 +158,7 @@ export class Query<T, R = T> {
|
|
141
158
|
this.repository,
|
142
159
|
this.table,
|
143
160
|
{
|
144
|
-
|
161
|
+
$all: (this.$all || []).concat(queries)
|
145
162
|
},
|
146
163
|
this
|
147
164
|
);
|
@@ -152,7 +169,7 @@ export class Query<T, R = T> {
|
|
152
169
|
this.repository,
|
153
170
|
this.table,
|
154
171
|
{
|
155
|
-
|
172
|
+
$not: (this.$not || []).concat(queries)
|
156
173
|
},
|
157
174
|
this
|
158
175
|
);
|
@@ -163,14 +180,14 @@ export class Query<T, R = T> {
|
|
163
180
|
this.repository,
|
164
181
|
this.table,
|
165
182
|
{
|
166
|
-
|
183
|
+
$none: (this.$none || []).concat(queries)
|
167
184
|
},
|
168
185
|
this
|
169
186
|
);
|
170
187
|
}
|
171
188
|
|
172
189
|
filter(constraints: FilterConstraints<T>): Query<T, R>;
|
173
|
-
filter<F extends keyof T>(column: F, value: FilterConstraints<T[F]> |
|
190
|
+
filter<F extends keyof T>(column: F, value: FilterConstraints<T[F]> | DeepConstraint<T[F]>): Query<T, R>;
|
174
191
|
filter(a: any, b?: any): Query<T, R> {
|
175
192
|
if (arguments.length === 1) {
|
176
193
|
const constraints = a as FilterConstraints<T>;
|
@@ -182,7 +199,7 @@ export class Query<T, R = T> {
|
|
182
199
|
this.repository,
|
183
200
|
this.table,
|
184
201
|
{
|
185
|
-
|
202
|
+
$all: (this.$all || []).concat(queries)
|
186
203
|
},
|
187
204
|
this
|
188
205
|
);
|
@@ -193,7 +210,7 @@ export class Query<T, R = T> {
|
|
193
210
|
this.repository,
|
194
211
|
this.table,
|
195
212
|
{
|
196
|
-
|
213
|
+
$all: (this.$all || []).concat({ [column]: value })
|
197
214
|
},
|
198
215
|
this
|
199
216
|
);
|
@@ -201,12 +218,12 @@ export class Query<T, R = T> {
|
|
201
218
|
}
|
202
219
|
|
203
220
|
sort<F extends keyof T>(column: F, direction: SortDirection): Query<T, R> {
|
204
|
-
const sort = { ...this
|
221
|
+
const sort = { ...this.$sort, [column]: direction };
|
205
222
|
const q = new Query<T, R>(
|
206
223
|
this.repository,
|
207
224
|
this.table,
|
208
225
|
{
|
209
|
-
|
226
|
+
$sort: sort
|
210
227
|
},
|
211
228
|
this
|
212
229
|
);
|
@@ -236,19 +253,6 @@ export class Query<T, R = T> {
|
|
236
253
|
// TODO
|
237
254
|
return this;
|
238
255
|
}
|
239
|
-
|
240
|
-
toJSON() {
|
241
|
-
const _filter = {
|
242
|
-
_any: this._any,
|
243
|
-
_all: this._all,
|
244
|
-
_not: this._not,
|
245
|
-
_none: this._none
|
246
|
-
};
|
247
|
-
return {
|
248
|
-
_filter: Object.values(_filter).some(Boolean) ? _filter : undefined,
|
249
|
-
_sort: this._sort
|
250
|
-
};
|
251
|
-
}
|
252
256
|
}
|
253
257
|
|
254
258
|
export abstract class Repository<T> extends Query<T, Selectable<T>> {
|
@@ -300,13 +304,13 @@ export class RestRepository<T> extends Repository<T> {
|
|
300
304
|
}
|
301
305
|
|
302
306
|
async request(method: string, path: string, body?: unknown) {
|
303
|
-
const {
|
304
|
-
const resp: Response = await this.fetch(`${
|
307
|
+
const { databaseURL } = this.client.options;
|
308
|
+
const resp: Response = await this.fetch(`${databaseURL}${path}`, {
|
305
309
|
method,
|
306
310
|
headers: {
|
307
311
|
Accept: '*/*',
|
308
312
|
'Content-Type': 'application/json',
|
309
|
-
Authorization: `Bearer ${this.client.options.
|
313
|
+
Authorization: `Bearer ${this.client.options.apiKey}`
|
310
314
|
},
|
311
315
|
body: JSON.stringify(body)
|
312
316
|
});
|
@@ -333,7 +337,14 @@ export class RestRepository<T> extends Repository<T> {
|
|
333
337
|
}
|
334
338
|
|
335
339
|
async create(object: T): Promise<T> {
|
336
|
-
const
|
340
|
+
const body = { ...object } as Record<string, unknown>;
|
341
|
+
for (const key of Object.keys(body)) {
|
342
|
+
const value = body[key];
|
343
|
+
if (value && typeof value === 'object' && typeof (value as Record<string, unknown>).id === 'string') {
|
344
|
+
body[key] = (value as XataRecord).id;
|
345
|
+
}
|
346
|
+
}
|
347
|
+
const obj = await this.request('POST', `/tables/${this.table}/data`, body);
|
337
348
|
return this.client.initObject(this.table, obj);
|
338
349
|
}
|
339
350
|
|
@@ -357,7 +368,17 @@ export class RestRepository<T> extends Repository<T> {
|
|
357
368
|
}
|
358
369
|
|
359
370
|
async query<R>(query: Query<T, R>): Promise<R[]> {
|
360
|
-
const
|
371
|
+
const filter = {
|
372
|
+
$any: query.$any,
|
373
|
+
$all: query.$all,
|
374
|
+
$not: query.$not,
|
375
|
+
$none: query.$none
|
376
|
+
};
|
377
|
+
const body = {
|
378
|
+
filter: Object.values(filter).some(Boolean) ? filter : undefined,
|
379
|
+
sort: query.$sort
|
380
|
+
};
|
381
|
+
const result = await this.request('POST', `/tables/${this.table}/query`, body);
|
361
382
|
return result.records.map((record: object) => this.client.initObject(this.table, record));
|
362
383
|
}
|
363
384
|
}
|
@@ -374,8 +395,8 @@ export class RestRespositoryFactory implements RepositoryFactory {
|
|
374
395
|
|
375
396
|
export type XataClientOptions = {
|
376
397
|
fetch?: unknown;
|
377
|
-
|
378
|
-
|
398
|
+
databaseURL: string;
|
399
|
+
apiKey: string;
|
379
400
|
repositoryFactory?: RepositoryFactory;
|
380
401
|
};
|
381
402
|
|
@@ -399,14 +420,14 @@ export class BaseClient<D extends Record<string, Repository<any>>> {
|
|
399
420
|
const value = o[field];
|
400
421
|
|
401
422
|
if (value && typeof value === 'object') {
|
402
|
-
const {
|
403
|
-
if (Object.keys(value).find((col) =>
|
423
|
+
const { id } = value as any;
|
424
|
+
if (Object.keys(value).find((col) => col === 'id')) {
|
404
425
|
o[field] = this.initObject(linkTable, value);
|
405
|
-
} else if (
|
426
|
+
} else if (id) {
|
406
427
|
o[field] = {
|
407
|
-
|
428
|
+
id,
|
408
429
|
get: () => {
|
409
|
-
this.db[linkTable].read(
|
430
|
+
this.db[linkTable].read(id);
|
410
431
|
}
|
411
432
|
};
|
412
433
|
}
|
@@ -415,13 +436,13 @@ export class BaseClient<D extends Record<string, Repository<any>>> {
|
|
415
436
|
|
416
437
|
const db = this.db;
|
417
438
|
o.read = function () {
|
418
|
-
return db[table].read(o['
|
439
|
+
return db[table].read(o['id'] as string);
|
419
440
|
};
|
420
441
|
o.update = function (data: any) {
|
421
|
-
return db[table].update(o['
|
442
|
+
return db[table].update(o['id'] as string, data);
|
422
443
|
};
|
423
444
|
o.delete = function () {
|
424
|
-
return db[table].delete(o['
|
445
|
+
return db[table].delete(o['id'] as string);
|
425
446
|
};
|
426
447
|
|
427
448
|
for (const prop of ['read', 'update', 'delete']) {
|