@linabase/js 0.5.1 → 0.5.3
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 +2 -2
- package/dist/index.cjs +328 -25
- package/dist/index.d.cts +399 -1
- package/dist/index.d.ts +399 -1
- package/dist/index.js +328 -25
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @linabase/js
|
|
|
14
14
|
import { createClient } from "@linabase/js";
|
|
15
15
|
|
|
16
16
|
const linabase = createClient({
|
|
17
|
-
url: "https://
|
|
17
|
+
url: "https://linabase.com",
|
|
18
18
|
anonKey: "lb_your_anon_key",
|
|
19
19
|
});
|
|
20
20
|
```
|
|
@@ -118,7 +118,7 @@ const types = await linabase.generateTypes();
|
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
120
|
const admin = createClient({
|
|
121
|
-
url: "https://
|
|
121
|
+
url: "https://linabase.com",
|
|
122
122
|
serviceRoleKey: "lb_your_service_role_key",
|
|
123
123
|
});
|
|
124
124
|
|
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,23 @@ var DatabaseClient = class {
|
|
|
50
50
|
this.params = new URLSearchParams();
|
|
51
51
|
}
|
|
52
52
|
// ─── Query Methods ──────────────────────────────────────
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Pick which columns to return. Supports nested joins (`"*, comments(*)"`)
|
|
55
|
+
* and column aliases (`"full_name:name"`). When chained after a mutation
|
|
56
|
+
* (`insert` / `update` / `upsert` / `delete`), it switches the request to
|
|
57
|
+
* `Prefer: return=representation` so the modified rows come back.
|
|
58
|
+
*
|
|
59
|
+
* @param columns - Postgres-style column list. Defaults to `"*"`.
|
|
60
|
+
* @param options - `count` returns a row count alongside the data; `head`
|
|
61
|
+
* omits the rows entirely (useful for count-only queries).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* await linabase.from("posts").select("id, title");
|
|
66
|
+
* await linabase.from("posts").select("*, author:profiles(name)");
|
|
67
|
+
* await linabase.from("posts").select("*", { count: "exact", head: true });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
54
70
|
select(columns, options) {
|
|
55
71
|
const isMutation = this.method === "POST" || this.method === "PATCH" || this.method === "DELETE";
|
|
56
72
|
if (!isMutation) {
|
|
@@ -62,6 +78,15 @@ var DatabaseClient = class {
|
|
|
62
78
|
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
63
79
|
return this;
|
|
64
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Insert one row or many. Returns the inserted rows by default.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* await linabase.from("posts").insert({ title: "Hi" });
|
|
87
|
+
* await linabase.from("posts").insert([{ title: "A" }, { title: "B" }]);
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
65
90
|
insert(data, options) {
|
|
66
91
|
this.method = "POST";
|
|
67
92
|
this.body = data;
|
|
@@ -83,12 +108,36 @@ var DatabaseClient = class {
|
|
|
83
108
|
if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
|
|
84
109
|
return this;
|
|
85
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Update rows that match the active filters.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* await linabase
|
|
117
|
+
* .from("posts")
|
|
118
|
+
* .update({ published: true })
|
|
119
|
+
* .eq("id", 42);
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
86
122
|
update(data) {
|
|
87
123
|
this.method = "PATCH";
|
|
88
124
|
this.body = data;
|
|
89
125
|
this.preferHeaders.push("return=representation");
|
|
90
126
|
return this;
|
|
91
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Delete rows that match the active filters. **Always** combine with at
|
|
130
|
+
* least one filter (e.g. `.eq("id", x)`) — calling `.delete()` on a bare
|
|
131
|
+
* builder removes every row in the table.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* await linabase
|
|
136
|
+
* .from("posts")
|
|
137
|
+
* .delete()
|
|
138
|
+
* .eq("id", 42);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
92
141
|
delete(options) {
|
|
93
142
|
this.method = "DELETE";
|
|
94
143
|
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
@@ -115,36 +164,36 @@ var DatabaseClient = class {
|
|
|
115
164
|
}
|
|
116
165
|
// ─── Comparison Filters ─────────────────────────────────
|
|
117
166
|
eq(column, value) {
|
|
118
|
-
this.params.
|
|
167
|
+
this.params.append(column, `eq.${value}`);
|
|
119
168
|
return this;
|
|
120
169
|
}
|
|
121
170
|
neq(column, value) {
|
|
122
|
-
this.params.
|
|
171
|
+
this.params.append(column, `neq.${value}`);
|
|
123
172
|
return this;
|
|
124
173
|
}
|
|
125
174
|
gt(column, value) {
|
|
126
|
-
this.params.
|
|
175
|
+
this.params.append(column, `gt.${value}`);
|
|
127
176
|
return this;
|
|
128
177
|
}
|
|
129
178
|
gte(column, value) {
|
|
130
|
-
this.params.
|
|
179
|
+
this.params.append(column, `gte.${value}`);
|
|
131
180
|
return this;
|
|
132
181
|
}
|
|
133
182
|
lt(column, value) {
|
|
134
|
-
this.params.
|
|
183
|
+
this.params.append(column, `lt.${value}`);
|
|
135
184
|
return this;
|
|
136
185
|
}
|
|
137
186
|
lte(column, value) {
|
|
138
|
-
this.params.
|
|
187
|
+
this.params.append(column, `lte.${value}`);
|
|
139
188
|
return this;
|
|
140
189
|
}
|
|
141
190
|
// ─── Pattern Matching ───────────────────────────────────
|
|
142
191
|
like(column, pattern) {
|
|
143
|
-
this.params.
|
|
192
|
+
this.params.append(column, `like.${pattern}`);
|
|
144
193
|
return this;
|
|
145
194
|
}
|
|
146
195
|
ilike(column, pattern) {
|
|
147
|
-
this.params.
|
|
196
|
+
this.params.append(column, `ilike.${pattern}`);
|
|
148
197
|
return this;
|
|
149
198
|
}
|
|
150
199
|
/** Regex match (~) when called with (column, pattern). Object-based multi-column filter when called with ({ col: val }). */
|
|
@@ -154,76 +203,76 @@ var DatabaseClient = class {
|
|
|
154
203
|
this.eq(key, value);
|
|
155
204
|
}
|
|
156
205
|
} else if (pattern !== void 0) {
|
|
157
|
-
this.params.
|
|
206
|
+
this.params.append(columnOrFilter, `match.${pattern}`);
|
|
158
207
|
}
|
|
159
208
|
return this;
|
|
160
209
|
}
|
|
161
210
|
/** Case-insensitive regex match (~*) */
|
|
162
211
|
imatch(column, pattern) {
|
|
163
|
-
this.params.
|
|
212
|
+
this.params.append(column, `imatch.${pattern}`);
|
|
164
213
|
return this;
|
|
165
214
|
}
|
|
166
215
|
// ─── Array/Set Filters ──────────────────────────────────
|
|
167
216
|
in(column, values) {
|
|
168
|
-
this.params.
|
|
217
|
+
this.params.append(column, `in.(${values.join(",")})`);
|
|
169
218
|
return this;
|
|
170
219
|
}
|
|
171
220
|
/** Contains (@>) - array or JSONB containment */
|
|
172
221
|
contains(column, value) {
|
|
173
|
-
this.params.
|
|
222
|
+
this.params.append(column, `cs.${JSON.stringify(value)}`);
|
|
174
223
|
return this;
|
|
175
224
|
}
|
|
176
225
|
/** Contained by (<@) */
|
|
177
226
|
containedBy(column, value) {
|
|
178
|
-
this.params.
|
|
227
|
+
this.params.append(column, `cd.${JSON.stringify(value)}`);
|
|
179
228
|
return this;
|
|
180
229
|
}
|
|
181
230
|
/** Overlap (&&) - ranges or arrays */
|
|
182
231
|
overlaps(column, value) {
|
|
183
|
-
this.params.
|
|
232
|
+
this.params.append(column, `ov.${JSON.stringify(value)}`);
|
|
184
233
|
return this;
|
|
185
234
|
}
|
|
186
235
|
// ─── Null/Boolean ───────────────────────────────────────
|
|
187
236
|
is(column, value) {
|
|
188
237
|
const v = value === null ? "null" : String(value);
|
|
189
|
-
this.params.
|
|
238
|
+
this.params.append(column, `is.${v}`);
|
|
190
239
|
return this;
|
|
191
240
|
}
|
|
192
241
|
/** IS DISTINCT FROM */
|
|
193
242
|
isDistinct(column, value) {
|
|
194
|
-
this.params.
|
|
243
|
+
this.params.append(column, `isdistinct.${value}`);
|
|
195
244
|
return this;
|
|
196
245
|
}
|
|
197
246
|
// ─── Range Type Filters ──────────────────────────────────
|
|
198
247
|
/** Strictly left of: [a,b] << [c,d] */
|
|
199
248
|
rangeLt(column, range) {
|
|
200
|
-
this.params.
|
|
249
|
+
this.params.append(column, `sl.${range}`);
|
|
201
250
|
return this;
|
|
202
251
|
}
|
|
203
252
|
/** Strictly right of: [a,b] >> [c,d] */
|
|
204
253
|
rangeGt(column, range) {
|
|
205
|
-
this.params.
|
|
254
|
+
this.params.append(column, `sr.${range}`);
|
|
206
255
|
return this;
|
|
207
256
|
}
|
|
208
257
|
/** Does not extend to the left of */
|
|
209
258
|
rangeGte(column, range) {
|
|
210
|
-
this.params.
|
|
259
|
+
this.params.append(column, `nxl.${range}`);
|
|
211
260
|
return this;
|
|
212
261
|
}
|
|
213
262
|
/** Does not extend to the right of */
|
|
214
263
|
rangeLte(column, range) {
|
|
215
|
-
this.params.
|
|
264
|
+
this.params.append(column, `nxr.${range}`);
|
|
216
265
|
return this;
|
|
217
266
|
}
|
|
218
267
|
/** Range is adjacent to */
|
|
219
268
|
rangeAdjacent(column, range) {
|
|
220
|
-
this.params.
|
|
269
|
+
this.params.append(column, `adj.${range}`);
|
|
221
270
|
return this;
|
|
222
271
|
}
|
|
223
272
|
// ─── Negation ───────────────────────────────────────────
|
|
224
273
|
/** Negate a filter: not.eq, not.in, not.is, etc. */
|
|
225
274
|
not(column, operator, value) {
|
|
226
|
-
this.params.
|
|
275
|
+
this.params.append(column, `not.${operator}.${value}`);
|
|
227
276
|
return this;
|
|
228
277
|
}
|
|
229
278
|
// ─── Logical ────────────────────────────────────────────
|
|
@@ -241,7 +290,7 @@ var DatabaseClient = class {
|
|
|
241
290
|
}
|
|
242
291
|
/** Generic filter: filter("column", "operator", "value") */
|
|
243
292
|
filter(column, operator, value) {
|
|
244
|
-
this.params.
|
|
293
|
+
this.params.append(column, `${operator}.${value}`);
|
|
245
294
|
return this;
|
|
246
295
|
}
|
|
247
296
|
// ─── Full-Text Search ───────────────────────────────────
|
|
@@ -249,7 +298,7 @@ var DatabaseClient = class {
|
|
|
249
298
|
textSearch(column, query, options) {
|
|
250
299
|
const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
|
|
251
300
|
const config = options?.config ? `(${options.config})` : "";
|
|
252
|
-
this.params.
|
|
301
|
+
this.params.append(column, `${op}${config}.${query}`);
|
|
253
302
|
return this;
|
|
254
303
|
}
|
|
255
304
|
// ─── Ordering & Pagination ──────────────────────────────
|
|
@@ -530,6 +579,24 @@ var BucketClient = class {
|
|
|
530
579
|
this.bucket = bucket;
|
|
531
580
|
this.baseUrl = baseUrl || "";
|
|
532
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Upload a file to the bucket at the given path.
|
|
584
|
+
*
|
|
585
|
+
* @param path - Object key inside the bucket (e.g. `userId/avatar.png`).
|
|
586
|
+
* @param file - File contents as `Blob`, `File`, or `ArrayBuffer`.
|
|
587
|
+
* @param options.contentType - Override the content type. Inferred from
|
|
588
|
+
* `File`/`Blob` when not set.
|
|
589
|
+
* @param options.upsert - If true, replaces an existing object at the same
|
|
590
|
+
* path. Defaults to false (returns an error if the path is taken).
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```ts
|
|
594
|
+
* const file = e.target.files![0];
|
|
595
|
+
* await linabase.storage
|
|
596
|
+
* .from("avatars")
|
|
597
|
+
* .upload(`${userId}/avatar.png`, file, { upsert: true });
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
533
600
|
async upload(path, file, options) {
|
|
534
601
|
try {
|
|
535
602
|
const formData = new FormData();
|
|
@@ -551,6 +618,18 @@ var BucketClient = class {
|
|
|
551
618
|
return { data: null, error: { message: err.message } };
|
|
552
619
|
}
|
|
553
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Download an object as a `Blob`.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* const { data } = await linabase.storage.from("docs").download("file.pdf");
|
|
627
|
+
* if (data) {
|
|
628
|
+
* const url = URL.createObjectURL(data);
|
|
629
|
+
* window.open(url);
|
|
630
|
+
* }
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
554
633
|
async download(path) {
|
|
555
634
|
try {
|
|
556
635
|
const res = await this.request(
|
|
@@ -599,6 +678,19 @@ var BucketClient = class {
|
|
|
599
678
|
return { data: null, error: { message: err.message } };
|
|
600
679
|
}
|
|
601
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Build a public URL for an object in a public bucket. No request is made;
|
|
683
|
+
* this just constructs the URL synchronously.
|
|
684
|
+
*
|
|
685
|
+
* For private buckets, use {@link createSignedUrl} instead.
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* ```ts
|
|
689
|
+
* const { data: { publicUrl } } = linabase.storage
|
|
690
|
+
* .from("avatars")
|
|
691
|
+
* .getPublicUrl(`${userId}/avatar.png`, { transform: { width: 64, height: 64 } });
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
602
694
|
getPublicUrl(path, options) {
|
|
603
695
|
let url;
|
|
604
696
|
if (options?.transform) {
|
|
@@ -617,6 +709,18 @@ var BucketClient = class {
|
|
|
617
709
|
}
|
|
618
710
|
return { data: { publicUrl: url } };
|
|
619
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Generate a time-limited signed URL for a private object. Valid for
|
|
714
|
+
* `expiresIn` seconds.
|
|
715
|
+
*
|
|
716
|
+
* @example
|
|
717
|
+
* ```ts
|
|
718
|
+
* const { data } = await linabase.storage
|
|
719
|
+
* .from("docs")
|
|
720
|
+
* .createSignedUrl(`${userId}/report.pdf`, 3600);
|
|
721
|
+
* if (data) window.open(data.signedUrl);
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
620
724
|
async createSignedUrl(path, expiresIn) {
|
|
621
725
|
try {
|
|
622
726
|
const res = await this.request(
|
|
@@ -787,6 +891,22 @@ var AuthClient = class {
|
|
|
787
891
|
}
|
|
788
892
|
}
|
|
789
893
|
// ─── Email/Password ────────────────────────────────────────
|
|
894
|
+
/**
|
|
895
|
+
* Create a new user account with email + password.
|
|
896
|
+
*
|
|
897
|
+
* On success, the user is signed in and the session is persisted. Pass
|
|
898
|
+
* extra metadata via `data`; it is stored on the user record and
|
|
899
|
+
* available later as `user.user_metadata`.
|
|
900
|
+
*
|
|
901
|
+
* @example
|
|
902
|
+
* ```ts
|
|
903
|
+
* const { data, error } = await linabase.auth.signUp({
|
|
904
|
+
* email: "alice@example.com",
|
|
905
|
+
* password: "correct horse battery staple",
|
|
906
|
+
* data: { full_name: "Alice" },
|
|
907
|
+
* });
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
790
910
|
async signUp(params) {
|
|
791
911
|
try {
|
|
792
912
|
const res = await this.request("/auth/v1/signup", {
|
|
@@ -803,6 +923,21 @@ var AuthClient = class {
|
|
|
803
923
|
return { data: null, error: { message: err.message } };
|
|
804
924
|
}
|
|
805
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Sign in an existing user with email + password.
|
|
928
|
+
*
|
|
929
|
+
* Alias of {@link signInWithPassword}. On success, the session is persisted
|
|
930
|
+
* and subsequent SDK calls use the returned access token automatically.
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```ts
|
|
934
|
+
* const { data, error } = await linabase.auth.signIn({
|
|
935
|
+
* email: "alice@example.com",
|
|
936
|
+
* password: "correct horse battery staple",
|
|
937
|
+
* });
|
|
938
|
+
* if (error) console.error(error.message);
|
|
939
|
+
* ```
|
|
940
|
+
*/
|
|
806
941
|
async signIn(params) {
|
|
807
942
|
try {
|
|
808
943
|
const res = await this.request("/auth/v1/token?grant_type=password", {
|
|
@@ -844,6 +979,105 @@ var AuthClient = class {
|
|
|
844
979
|
return { data: null, error: { message: err.message } };
|
|
845
980
|
}
|
|
846
981
|
}
|
|
982
|
+
// ─── Device-bound (Mobile) ──────────────────────────────────
|
|
983
|
+
/**
|
|
984
|
+
* Sign in with a device-bound secret. Intended for mobile clients that
|
|
985
|
+
* generate a random secret on first launch and persist it in the device
|
|
986
|
+
* keychain. No email or password is involved.
|
|
987
|
+
*
|
|
988
|
+
* Pass `create: true` on first launch to register a new anonymous user.
|
|
989
|
+
* Subsequent launches should call without `create` (or with `create: false`)
|
|
990
|
+
* to refuse silent account creation if the keychain entry was lost.
|
|
991
|
+
*
|
|
992
|
+
* Requires "Device-bound sign-ins" to be enabled in the project's Auth
|
|
993
|
+
* Settings.
|
|
994
|
+
*
|
|
995
|
+
* @example
|
|
996
|
+
* ```ts
|
|
997
|
+
* const secret = getOrCreateDeviceSecret();
|
|
998
|
+
* const { data, error } = await linabase.auth.signInWithDevice({
|
|
999
|
+
* deviceSecret: secret,
|
|
1000
|
+
* create: true,
|
|
1001
|
+
* });
|
|
1002
|
+
* ```
|
|
1003
|
+
*/
|
|
1004
|
+
async signInWithDevice(params) {
|
|
1005
|
+
try {
|
|
1006
|
+
const res = await this.request("/auth/v1/token?grant_type=device", {
|
|
1007
|
+
method: "POST",
|
|
1008
|
+
body: JSON.stringify({
|
|
1009
|
+
device_secret: params.deviceSecret,
|
|
1010
|
+
create: params.create,
|
|
1011
|
+
data: params.data
|
|
1012
|
+
})
|
|
1013
|
+
});
|
|
1014
|
+
const data = await res.json();
|
|
1015
|
+
if (!res.ok) return { data: null, error: data };
|
|
1016
|
+
const session = this.parseAuthResponse(data);
|
|
1017
|
+
this.setSession(session, true);
|
|
1018
|
+
this.emit("SIGNED_IN", session);
|
|
1019
|
+
return { data: session, error: null };
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
return { data: null, error: { message: err.message } };
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
// ─── Game Center (iOS) ──────────────────────────────────────
|
|
1025
|
+
/**
|
|
1026
|
+
* Sign in with an Apple Game Center identity assertion. The mobile client
|
|
1027
|
+
* obtains the assertion fields via
|
|
1028
|
+
* `GKLocalPlayer.local.fetchItems(forIdentityVerificationSignature:)` (iOS
|
|
1029
|
+
* 13.5+) and forwards them here. The project must have the app's bundle ID
|
|
1030
|
+
* configured in the Game Center allowlist.
|
|
1031
|
+
*/
|
|
1032
|
+
async signInWithGameCenter(params) {
|
|
1033
|
+
try {
|
|
1034
|
+
const res = await this.request("/auth/v1/token?grant_type=game_center", {
|
|
1035
|
+
method: "POST",
|
|
1036
|
+
body: JSON.stringify({
|
|
1037
|
+
player_id: params.playerId,
|
|
1038
|
+
bundle_id: params.bundleId,
|
|
1039
|
+
public_key_url: params.publicKeyURL,
|
|
1040
|
+
signature: params.signature,
|
|
1041
|
+
salt: params.salt,
|
|
1042
|
+
timestamp: params.timestamp,
|
|
1043
|
+
display_name: params.displayName
|
|
1044
|
+
})
|
|
1045
|
+
});
|
|
1046
|
+
const data = await res.json();
|
|
1047
|
+
if (!res.ok) return { data: null, error: data };
|
|
1048
|
+
const session = this.parseAuthResponse(data);
|
|
1049
|
+
this.setSession(session, true);
|
|
1050
|
+
this.emit("SIGNED_IN", session);
|
|
1051
|
+
return { data: session, error: null };
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
return { data: null, error: { message: err.message } };
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
// ─── Google Play Games (Android) ────────────────────────────
|
|
1057
|
+
/**
|
|
1058
|
+
* Sign in with a Google Play Games server auth code obtained via
|
|
1059
|
+
* `PlayGamesSignInClient.requestServerSideAccess(serverClientId)`. The
|
|
1060
|
+
* project must have the matching OAuth client configured in Auth Settings.
|
|
1061
|
+
*/
|
|
1062
|
+
async signInWithPlayGames(params) {
|
|
1063
|
+
try {
|
|
1064
|
+
const res = await this.request("/auth/v1/token?grant_type=play_games", {
|
|
1065
|
+
method: "POST",
|
|
1066
|
+
body: JSON.stringify({
|
|
1067
|
+
server_auth_code: params.serverAuthCode,
|
|
1068
|
+
redirect_uri: params.redirectUri
|
|
1069
|
+
})
|
|
1070
|
+
});
|
|
1071
|
+
const data = await res.json();
|
|
1072
|
+
if (!res.ok) return { data: null, error: data };
|
|
1073
|
+
const session = this.parseAuthResponse(data);
|
|
1074
|
+
this.setSession(session, true);
|
|
1075
|
+
this.emit("SIGNED_IN", session);
|
|
1076
|
+
return { data: session, error: null };
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
return { data: null, error: { message: err.message } };
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
847
1081
|
// ─── OAuth ─────────────────────────────────────────────────
|
|
848
1082
|
signInWithOAuth(params) {
|
|
849
1083
|
const queryParams = new URLSearchParams({ provider: params.provider });
|
|
@@ -857,6 +1091,16 @@ var AuthClient = class {
|
|
|
857
1091
|
return { url };
|
|
858
1092
|
}
|
|
859
1093
|
// ─── Session Management ────────────────────────────────────
|
|
1094
|
+
/**
|
|
1095
|
+
* Sign the current user out, clear the persisted session, and revoke the
|
|
1096
|
+
* refresh token on the server. Fires a `SIGNED_OUT` event to listeners
|
|
1097
|
+
* registered with `onAuthStateChange`.
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```ts
|
|
1101
|
+
* await linabase.auth.signOut();
|
|
1102
|
+
* ```
|
|
1103
|
+
*/
|
|
860
1104
|
async signOut() {
|
|
861
1105
|
try {
|
|
862
1106
|
await this.request("/auth/v1/logout", { method: "POST" });
|
|
@@ -867,6 +1111,17 @@ var AuthClient = class {
|
|
|
867
1111
|
return { error: { message: err.message } };
|
|
868
1112
|
}
|
|
869
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Return the current persisted session. Auto-refreshes if the access
|
|
1116
|
+
* token is expired and a refresh token is available. Returns
|
|
1117
|
+
* `{ session: null }` when the user isn't signed in.
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* const { data: { session } } = await linabase.auth.getSession();
|
|
1122
|
+
* if (session) console.log("Signed in as", session.user.email);
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
870
1125
|
async getSession() {
|
|
871
1126
|
if (this.currentSession) {
|
|
872
1127
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -1067,6 +1322,41 @@ var AuthClient = class {
|
|
|
1067
1322
|
} catch (err) {
|
|
1068
1323
|
return { error: { message: err.message } };
|
|
1069
1324
|
}
|
|
1325
|
+
},
|
|
1326
|
+
async listSessions(userId) {
|
|
1327
|
+
try {
|
|
1328
|
+
const res = await request(`/auth/v1/admin/users/${userId}/sessions`);
|
|
1329
|
+
const data = await res.json();
|
|
1330
|
+
if (!res.ok) return { data: null, error: data };
|
|
1331
|
+
return { data: data.sessions, error: null };
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
return { data: null, error: { message: err.message } };
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
async deleteSession(userId, sessionId) {
|
|
1337
|
+
try {
|
|
1338
|
+
const res = await request(`/auth/v1/admin/users/${userId}/sessions/${sessionId}`, {
|
|
1339
|
+
method: "DELETE"
|
|
1340
|
+
});
|
|
1341
|
+
const data = await res.json().catch(() => ({}));
|
|
1342
|
+
if (!res.ok) return { data: null, error: data };
|
|
1343
|
+
return { data, error: null };
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
return { data: null, error: { message: err.message } };
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
async deleteOtherSessions(userId, exceptSessionId) {
|
|
1349
|
+
try {
|
|
1350
|
+
const qs = exceptSessionId ? `?except=${encodeURIComponent(exceptSessionId)}` : "";
|
|
1351
|
+
const res = await request(`/auth/v1/admin/users/${userId}/sessions${qs}`, {
|
|
1352
|
+
method: "DELETE"
|
|
1353
|
+
});
|
|
1354
|
+
const data = await res.json().catch(() => ({}));
|
|
1355
|
+
if (!res.ok) return { data: null, error: data };
|
|
1356
|
+
return { data, error: null };
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
return { data: null, error: { message: err.message } };
|
|
1359
|
+
}
|
|
1070
1360
|
}
|
|
1071
1361
|
};
|
|
1072
1362
|
}
|
|
@@ -1180,6 +1470,19 @@ var FunctionsClient = class {
|
|
|
1180
1470
|
constructor(request) {
|
|
1181
1471
|
this.request = request;
|
|
1182
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Call a function by name.
|
|
1475
|
+
*
|
|
1476
|
+
* @param name - Function slug as registered on the project.
|
|
1477
|
+
* @param options - Body, method, and optional extra headers.
|
|
1478
|
+
* @returns `{ data, error }` — `data` is the parsed JSON response, `error`
|
|
1479
|
+
* is an object with `message` if the call failed (HTTP non-2xx or thrown).
|
|
1480
|
+
*
|
|
1481
|
+
* @example
|
|
1482
|
+
* ```ts
|
|
1483
|
+
* await linabase.functions.invoke("ping");
|
|
1484
|
+
* ```
|
|
1485
|
+
*/
|
|
1183
1486
|
async invoke(name, options) {
|
|
1184
1487
|
try {
|
|
1185
1488
|
const method = options?.method || "POST";
|