@linabase/js 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -57,10 +57,11 @@ var DatabaseClient = class {
57
57
  if (options?.count) this.preferHeaders.push(`count=${options.count}`);
58
58
  return this;
59
59
  }
60
- insert(data) {
60
+ insert(data, options) {
61
61
  this.method = "POST";
62
62
  this.body = data;
63
63
  this.preferHeaders.push("return=representation");
64
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
64
65
  return this;
65
66
  }
66
67
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
@@ -74,6 +75,7 @@ var DatabaseClient = class {
74
75
  if (options?.onConflict) {
75
76
  this.params.set("on_conflict", options.onConflict);
76
77
  }
78
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
77
79
  return this;
78
80
  }
79
81
  update(data) {
@@ -82,8 +84,28 @@ var DatabaseClient = class {
82
84
  this.preferHeaders.push("return=representation");
83
85
  return this;
84
86
  }
85
- delete() {
87
+ delete(options) {
86
88
  this.method = "DELETE";
89
+ if (options?.count) this.preferHeaders.push(`count=${options.count}`);
90
+ return this;
91
+ }
92
+ /** Specify return=minimal to skip returning data on mutations (better performance). */
93
+ returning(mode = "representation") {
94
+ this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("return="));
95
+ this.preferHeaders.push(`return=${mode}`);
96
+ return this;
97
+ }
98
+ /** Get the query execution plan (EXPLAIN). */
99
+ explain(options) {
100
+ const parts = ["plan"];
101
+ if (options?.analyze) parts.push("analyze");
102
+ if (options?.verbose) parts.push("verbose");
103
+ if (options?.settings) parts.push("settings");
104
+ if (options?.buffers) parts.push("buffers");
105
+ if (options?.wal) parts.push("wal");
106
+ if (options?.format) parts.push(`format:${options.format}`);
107
+ this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("plan"));
108
+ this.preferHeaders.push(parts.join("|"));
87
109
  return this;
88
110
  }
89
111
  // ─── Comparison Filters ─────────────────────────────────
@@ -167,6 +189,32 @@ var DatabaseClient = class {
167
189
  this.params.set(column, `isdistinct.${value}`);
168
190
  return this;
169
191
  }
192
+ // ─── Range Type Filters ──────────────────────────────────
193
+ /** Strictly left of: [a,b] << [c,d] */
194
+ rangeLt(column, range) {
195
+ this.params.set(column, `sl.${range}`);
196
+ return this;
197
+ }
198
+ /** Strictly right of: [a,b] >> [c,d] */
199
+ rangeGt(column, range) {
200
+ this.params.set(column, `sr.${range}`);
201
+ return this;
202
+ }
203
+ /** Does not extend to the left of */
204
+ rangeGte(column, range) {
205
+ this.params.set(column, `nxl.${range}`);
206
+ return this;
207
+ }
208
+ /** Does not extend to the right of */
209
+ rangeLte(column, range) {
210
+ this.params.set(column, `nxr.${range}`);
211
+ return this;
212
+ }
213
+ /** Range is adjacent to */
214
+ rangeAdjacent(column, range) {
215
+ this.params.set(column, `adj.${range}`);
216
+ return this;
217
+ }
170
218
  // ─── Negation ───────────────────────────────────────────
171
219
  /** Negate a filter: not.eq, not.in, not.is, etc. */
172
220
  not(column, operator, value) {
@@ -175,29 +223,53 @@ var DatabaseClient = class {
175
223
  }
176
224
  // ─── Logical ────────────────────────────────────────────
177
225
  /** OR filter: or("age.gt.20,name.eq.John") */
178
- or(filters) {
179
- this.params.set("or", `(${filters})`);
226
+ or(filters, options) {
227
+ const key = options?.foreignTable ? `${options.foreignTable}.or` : "or";
228
+ this.params.set(key, `(${filters})`);
229
+ return this;
230
+ }
231
+ /** AND filter: and("age.gt.20,name.eq.John") */
232
+ and(filters, options) {
233
+ const key = options?.foreignTable ? `${options.foreignTable}.and` : "and";
234
+ this.params.set(key, `(${filters})`);
235
+ return this;
236
+ }
237
+ /** Generic filter: filter("column", "operator", "value") */
238
+ filter(column, operator, value) {
239
+ this.params.set(column, `${operator}.${value}`);
180
240
  return this;
181
241
  }
182
242
  // ─── Full-Text Search ───────────────────────────────────
183
- /** to_tsquery */
243
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
184
244
  textSearch(column, query, options) {
185
245
  const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
186
- this.params.set(column, `${op}.${query}`);
246
+ const config = options?.config ? `(${options.config})` : "";
247
+ this.params.set(column, `${op}${config}.${query}`);
187
248
  return this;
188
249
  }
189
250
  // ─── Ordering & Pagination ──────────────────────────────
190
251
  order(column, options) {
191
252
  const dir = options?.ascending === false ? "desc" : "asc";
192
253
  const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
193
- const existing = this.params.get("order");
254
+ const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
255
+ const existing = this.params.get(key);
194
256
  if (existing) {
195
- this.params.set("order", `${existing},${column}.${dir}${nulls}`);
257
+ this.params.set(key, `${existing},${column}.${dir}${nulls}`);
196
258
  } else {
197
- this.params.set("order", `${column}.${dir}${nulls}`);
259
+ this.params.set(key, `${column}.${dir}${nulls}`);
198
260
  }
199
261
  return this;
200
262
  }
263
+ /** Limit results on a foreign table */
264
+ limitForeignTable(count, foreignTable) {
265
+ this.params.set(`${foreignTable}.limit`, String(count));
266
+ return this;
267
+ }
268
+ /** Offset results on a foreign table */
269
+ offsetForeignTable(count, foreignTable) {
270
+ this.params.set(`${foreignTable}.offset`, String(count));
271
+ return this;
272
+ }
201
273
  limit(count) {
202
274
  this.params.set("limit", String(count));
203
275
  return this;
@@ -622,6 +694,8 @@ var BucketClient = class {
622
694
  var AuthClient = class {
623
695
  request;
624
696
  listeners = [];
697
+ /** Promise that resolves when session restore from storage is complete. */
698
+ _restorePromise = Promise.resolve();
625
699
  currentSession = null;
626
700
  /**
627
701
  * Callback that the parent LinabaseClient can set to update the Authorization
@@ -992,6 +1066,11 @@ var AuthClient = class {
992
1066
  // ─── Auth State Listener ───────────────────────────────────
993
1067
  onAuthStateChange(callback) {
994
1068
  this.listeners.push(callback);
1069
+ this._restorePromise.then(() => {
1070
+ if (this.listeners.includes(callback)) {
1071
+ callback("INITIAL_SESSION", this.currentSession);
1072
+ }
1073
+ });
995
1074
  return {
996
1075
  unsubscribe: () => {
997
1076
  this.listeners = this.listeners.filter((l) => l !== callback);
@@ -1089,7 +1168,7 @@ function createClient(config) {
1089
1168
  sessionStorage.removeItem(sessionKey);
1090
1169
  }
1091
1170
  });
1092
- Promise.resolve(sessionStorage.getItem(sessionKey)).then((raw) => {
1171
+ authClient._restorePromise = Promise.resolve(sessionStorage.getItem(sessionKey)).then((raw) => {
1093
1172
  if (raw) {
1094
1173
  try {
1095
1174
  authClient.setSession(JSON.parse(raw));
package/dist/index.d.cts CHANGED
@@ -39,14 +39,30 @@ declare class DatabaseClient {
39
39
  count?: "exact" | "estimated" | "planned";
40
40
  head?: boolean;
41
41
  }): this;
42
- insert(data: Record<string, any> | Record<string, any>[]): this;
42
+ insert(data: Record<string, any> | Record<string, any>[], options?: {
43
+ defaultToNull?: boolean;
44
+ }): this;
43
45
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
44
46
  upsert(data: Record<string, any> | Record<string, any>[], options?: {
45
47
  ignoreDuplicates?: boolean;
46
48
  onConflict?: string;
49
+ defaultToNull?: boolean;
47
50
  }): this;
48
51
  update(data: Record<string, any>): this;
49
- delete(): this;
52
+ delete(options?: {
53
+ count?: "exact" | "estimated" | "planned";
54
+ }): this;
55
+ /** Specify return=minimal to skip returning data on mutations (better performance). */
56
+ returning(mode?: "minimal" | "representation"): this;
57
+ /** Get the query execution plan (EXPLAIN). */
58
+ explain(options?: {
59
+ analyze?: boolean;
60
+ verbose?: boolean;
61
+ settings?: boolean;
62
+ buffers?: boolean;
63
+ wal?: boolean;
64
+ format?: "json" | "text";
65
+ }): this;
50
66
  eq(column: string, value: any): this;
51
67
  neq(column: string, value: any): this;
52
68
  gt(column: string, value: any): this;
@@ -69,18 +85,42 @@ declare class DatabaseClient {
69
85
  is(column: string, value: "null" | "true" | "false" | null | boolean): this;
70
86
  /** IS DISTINCT FROM */
71
87
  isDistinct(column: string, value: any): this;
88
+ /** Strictly left of: [a,b] << [c,d] */
89
+ rangeLt(column: string, range: string): this;
90
+ /** Strictly right of: [a,b] >> [c,d] */
91
+ rangeGt(column: string, range: string): this;
92
+ /** Does not extend to the left of */
93
+ rangeGte(column: string, range: string): this;
94
+ /** Does not extend to the right of */
95
+ rangeLte(column: string, range: string): this;
96
+ /** Range is adjacent to */
97
+ rangeAdjacent(column: string, range: string): this;
72
98
  /** Negate a filter: not.eq, not.in, not.is, etc. */
73
99
  not(column: string, operator: string, value: any): this;
74
100
  /** OR filter: or("age.gt.20,name.eq.John") */
75
- or(filters: string): this;
76
- /** to_tsquery */
101
+ or(filters: string, options?: {
102
+ foreignTable?: string;
103
+ }): this;
104
+ /** AND filter: and("age.gt.20,name.eq.John") */
105
+ and(filters: string, options?: {
106
+ foreignTable?: string;
107
+ }): this;
108
+ /** Generic filter: filter("column", "operator", "value") */
109
+ filter(column: string, operator: string, value: any): this;
110
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
77
111
  textSearch(column: string, query: string, options?: {
78
112
  type?: "plain" | "phrase" | "websearch";
113
+ config?: string;
79
114
  }): this;
80
115
  order(column: string, options?: {
81
116
  ascending?: boolean;
82
117
  nullsFirst?: boolean;
118
+ foreignTable?: string;
83
119
  }): this;
120
+ /** Limit results on a foreign table */
121
+ limitForeignTable(count: number, foreignTable: string): this;
122
+ /** Offset results on a foreign table */
123
+ offsetForeignTable(count: number, foreignTable: string): this;
84
124
  limit(count: number): this;
85
125
  offset(count: number): this;
86
126
  /** Request exact, estimated, or planned count via Prefer header */
@@ -226,6 +266,8 @@ interface AuthUser {
226
266
  declare class AuthClient {
227
267
  private request;
228
268
  private listeners;
269
+ /** Promise that resolves when session restore from storage is complete. */
270
+ _restorePromise: Promise<void>;
229
271
  private currentSession;
230
272
  /**
231
273
  * Callback that the parent LinabaseClient can set to update the Authorization
package/dist/index.d.ts CHANGED
@@ -39,14 +39,30 @@ declare class DatabaseClient {
39
39
  count?: "exact" | "estimated" | "planned";
40
40
  head?: boolean;
41
41
  }): this;
42
- insert(data: Record<string, any> | Record<string, any>[]): this;
42
+ insert(data: Record<string, any> | Record<string, any>[], options?: {
43
+ defaultToNull?: boolean;
44
+ }): this;
43
45
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
44
46
  upsert(data: Record<string, any> | Record<string, any>[], options?: {
45
47
  ignoreDuplicates?: boolean;
46
48
  onConflict?: string;
49
+ defaultToNull?: boolean;
47
50
  }): this;
48
51
  update(data: Record<string, any>): this;
49
- delete(): this;
52
+ delete(options?: {
53
+ count?: "exact" | "estimated" | "planned";
54
+ }): this;
55
+ /** Specify return=minimal to skip returning data on mutations (better performance). */
56
+ returning(mode?: "minimal" | "representation"): this;
57
+ /** Get the query execution plan (EXPLAIN). */
58
+ explain(options?: {
59
+ analyze?: boolean;
60
+ verbose?: boolean;
61
+ settings?: boolean;
62
+ buffers?: boolean;
63
+ wal?: boolean;
64
+ format?: "json" | "text";
65
+ }): this;
50
66
  eq(column: string, value: any): this;
51
67
  neq(column: string, value: any): this;
52
68
  gt(column: string, value: any): this;
@@ -69,18 +85,42 @@ declare class DatabaseClient {
69
85
  is(column: string, value: "null" | "true" | "false" | null | boolean): this;
70
86
  /** IS DISTINCT FROM */
71
87
  isDistinct(column: string, value: any): this;
88
+ /** Strictly left of: [a,b] << [c,d] */
89
+ rangeLt(column: string, range: string): this;
90
+ /** Strictly right of: [a,b] >> [c,d] */
91
+ rangeGt(column: string, range: string): this;
92
+ /** Does not extend to the left of */
93
+ rangeGte(column: string, range: string): this;
94
+ /** Does not extend to the right of */
95
+ rangeLte(column: string, range: string): this;
96
+ /** Range is adjacent to */
97
+ rangeAdjacent(column: string, range: string): this;
72
98
  /** Negate a filter: not.eq, not.in, not.is, etc. */
73
99
  not(column: string, operator: string, value: any): this;
74
100
  /** OR filter: or("age.gt.20,name.eq.John") */
75
- or(filters: string): this;
76
- /** to_tsquery */
101
+ or(filters: string, options?: {
102
+ foreignTable?: string;
103
+ }): this;
104
+ /** AND filter: and("age.gt.20,name.eq.John") */
105
+ and(filters: string, options?: {
106
+ foreignTable?: string;
107
+ }): this;
108
+ /** Generic filter: filter("column", "operator", "value") */
109
+ filter(column: string, operator: string, value: any): this;
110
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
77
111
  textSearch(column: string, query: string, options?: {
78
112
  type?: "plain" | "phrase" | "websearch";
113
+ config?: string;
79
114
  }): this;
80
115
  order(column: string, options?: {
81
116
  ascending?: boolean;
82
117
  nullsFirst?: boolean;
118
+ foreignTable?: string;
83
119
  }): this;
120
+ /** Limit results on a foreign table */
121
+ limitForeignTable(count: number, foreignTable: string): this;
122
+ /** Offset results on a foreign table */
123
+ offsetForeignTable(count: number, foreignTable: string): this;
84
124
  limit(count: number): this;
85
125
  offset(count: number): this;
86
126
  /** Request exact, estimated, or planned count via Prefer header */
@@ -226,6 +266,8 @@ interface AuthUser {
226
266
  declare class AuthClient {
227
267
  private request;
228
268
  private listeners;
269
+ /** Promise that resolves when session restore from storage is complete. */
270
+ _restorePromise: Promise<void>;
229
271
  private currentSession;
230
272
  /**
231
273
  * Callback that the parent LinabaseClient can set to update the Authorization
package/dist/index.js CHANGED
@@ -25,10 +25,11 @@ var DatabaseClient = class {
25
25
  if (options?.count) this.preferHeaders.push(`count=${options.count}`);
26
26
  return this;
27
27
  }
28
- insert(data) {
28
+ insert(data, options) {
29
29
  this.method = "POST";
30
30
  this.body = data;
31
31
  this.preferHeaders.push("return=representation");
32
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
32
33
  return this;
33
34
  }
34
35
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
@@ -42,6 +43,7 @@ var DatabaseClient = class {
42
43
  if (options?.onConflict) {
43
44
  this.params.set("on_conflict", options.onConflict);
44
45
  }
46
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
45
47
  return this;
46
48
  }
47
49
  update(data) {
@@ -50,8 +52,28 @@ var DatabaseClient = class {
50
52
  this.preferHeaders.push("return=representation");
51
53
  return this;
52
54
  }
53
- delete() {
55
+ delete(options) {
54
56
  this.method = "DELETE";
57
+ if (options?.count) this.preferHeaders.push(`count=${options.count}`);
58
+ return this;
59
+ }
60
+ /** Specify return=minimal to skip returning data on mutations (better performance). */
61
+ returning(mode = "representation") {
62
+ this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("return="));
63
+ this.preferHeaders.push(`return=${mode}`);
64
+ return this;
65
+ }
66
+ /** Get the query execution plan (EXPLAIN). */
67
+ explain(options) {
68
+ const parts = ["plan"];
69
+ if (options?.analyze) parts.push("analyze");
70
+ if (options?.verbose) parts.push("verbose");
71
+ if (options?.settings) parts.push("settings");
72
+ if (options?.buffers) parts.push("buffers");
73
+ if (options?.wal) parts.push("wal");
74
+ if (options?.format) parts.push(`format:${options.format}`);
75
+ this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("plan"));
76
+ this.preferHeaders.push(parts.join("|"));
55
77
  return this;
56
78
  }
57
79
  // ─── Comparison Filters ─────────────────────────────────
@@ -135,6 +157,32 @@ var DatabaseClient = class {
135
157
  this.params.set(column, `isdistinct.${value}`);
136
158
  return this;
137
159
  }
160
+ // ─── Range Type Filters ──────────────────────────────────
161
+ /** Strictly left of: [a,b] << [c,d] */
162
+ rangeLt(column, range) {
163
+ this.params.set(column, `sl.${range}`);
164
+ return this;
165
+ }
166
+ /** Strictly right of: [a,b] >> [c,d] */
167
+ rangeGt(column, range) {
168
+ this.params.set(column, `sr.${range}`);
169
+ return this;
170
+ }
171
+ /** Does not extend to the left of */
172
+ rangeGte(column, range) {
173
+ this.params.set(column, `nxl.${range}`);
174
+ return this;
175
+ }
176
+ /** Does not extend to the right of */
177
+ rangeLte(column, range) {
178
+ this.params.set(column, `nxr.${range}`);
179
+ return this;
180
+ }
181
+ /** Range is adjacent to */
182
+ rangeAdjacent(column, range) {
183
+ this.params.set(column, `adj.${range}`);
184
+ return this;
185
+ }
138
186
  // ─── Negation ───────────────────────────────────────────
139
187
  /** Negate a filter: not.eq, not.in, not.is, etc. */
140
188
  not(column, operator, value) {
@@ -143,29 +191,53 @@ var DatabaseClient = class {
143
191
  }
144
192
  // ─── Logical ────────────────────────────────────────────
145
193
  /** OR filter: or("age.gt.20,name.eq.John") */
146
- or(filters) {
147
- this.params.set("or", `(${filters})`);
194
+ or(filters, options) {
195
+ const key = options?.foreignTable ? `${options.foreignTable}.or` : "or";
196
+ this.params.set(key, `(${filters})`);
197
+ return this;
198
+ }
199
+ /** AND filter: and("age.gt.20,name.eq.John") */
200
+ and(filters, options) {
201
+ const key = options?.foreignTable ? `${options.foreignTable}.and` : "and";
202
+ this.params.set(key, `(${filters})`);
203
+ return this;
204
+ }
205
+ /** Generic filter: filter("column", "operator", "value") */
206
+ filter(column, operator, value) {
207
+ this.params.set(column, `${operator}.${value}`);
148
208
  return this;
149
209
  }
150
210
  // ─── Full-Text Search ───────────────────────────────────
151
- /** to_tsquery */
211
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
152
212
  textSearch(column, query, options) {
153
213
  const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
154
- this.params.set(column, `${op}.${query}`);
214
+ const config = options?.config ? `(${options.config})` : "";
215
+ this.params.set(column, `${op}${config}.${query}`);
155
216
  return this;
156
217
  }
157
218
  // ─── Ordering & Pagination ──────────────────────────────
158
219
  order(column, options) {
159
220
  const dir = options?.ascending === false ? "desc" : "asc";
160
221
  const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
161
- const existing = this.params.get("order");
222
+ const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
223
+ const existing = this.params.get(key);
162
224
  if (existing) {
163
- this.params.set("order", `${existing},${column}.${dir}${nulls}`);
225
+ this.params.set(key, `${existing},${column}.${dir}${nulls}`);
164
226
  } else {
165
- this.params.set("order", `${column}.${dir}${nulls}`);
227
+ this.params.set(key, `${column}.${dir}${nulls}`);
166
228
  }
167
229
  return this;
168
230
  }
231
+ /** Limit results on a foreign table */
232
+ limitForeignTable(count, foreignTable) {
233
+ this.params.set(`${foreignTable}.limit`, String(count));
234
+ return this;
235
+ }
236
+ /** Offset results on a foreign table */
237
+ offsetForeignTable(count, foreignTable) {
238
+ this.params.set(`${foreignTable}.offset`, String(count));
239
+ return this;
240
+ }
169
241
  limit(count) {
170
242
  this.params.set("limit", String(count));
171
243
  return this;
@@ -590,6 +662,8 @@ var BucketClient = class {
590
662
  var AuthClient = class {
591
663
  request;
592
664
  listeners = [];
665
+ /** Promise that resolves when session restore from storage is complete. */
666
+ _restorePromise = Promise.resolve();
593
667
  currentSession = null;
594
668
  /**
595
669
  * Callback that the parent LinabaseClient can set to update the Authorization
@@ -960,6 +1034,11 @@ var AuthClient = class {
960
1034
  // ─── Auth State Listener ───────────────────────────────────
961
1035
  onAuthStateChange(callback) {
962
1036
  this.listeners.push(callback);
1037
+ this._restorePromise.then(() => {
1038
+ if (this.listeners.includes(callback)) {
1039
+ callback("INITIAL_SESSION", this.currentSession);
1040
+ }
1041
+ });
963
1042
  return {
964
1043
  unsubscribe: () => {
965
1044
  this.listeners = this.listeners.filter((l) => l !== callback);
@@ -1057,7 +1136,7 @@ function createClient(config) {
1057
1136
  sessionStorage.removeItem(sessionKey);
1058
1137
  }
1059
1138
  });
1060
- Promise.resolve(sessionStorage.getItem(sessionKey)).then((raw) => {
1139
+ authClient._restorePromise = Promise.resolve(sessionStorage.getItem(sessionKey)).then((raw) => {
1061
1140
  if (raw) {
1062
1141
  try {
1063
1142
  authClient.setSession(JSON.parse(raw));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linabase/js",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "JavaScript/TypeScript client SDK for Linabase (database, storage, auth)",
5
5
  "license": "MIT",
6
6
  "type": "module",