@fluxbase/sdk 0.0.1-rc.30 → 0.0.1-rc.32

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.js CHANGED
@@ -3959,8 +3959,12 @@ var QueryBuilder = class {
3959
3959
  constructor(fetch2, table) {
3960
3960
  this.selectQuery = "*";
3961
3961
  this.filters = [];
3962
+ this.orFilters = [];
3963
+ this.andFilters = [];
3962
3964
  this.orderBys = [];
3963
3965
  this.singleRow = false;
3966
+ this.maybeSingleRow = false;
3967
+ this.operationType = "select";
3964
3968
  this.fetch = fetch2;
3965
3969
  this.table = table;
3966
3970
  }
@@ -3977,27 +3981,35 @@ var QueryBuilder = class {
3977
3981
  /**
3978
3982
  * Insert a single row or multiple rows
3979
3983
  */
3980
- async insert(data) {
3981
- const body = Array.isArray(data) ? data : data;
3982
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
3983
- return {
3984
- data: response,
3985
- error: null,
3986
- count: Array.isArray(data) ? data.length : 1,
3987
- status: 201,
3988
- statusText: "Created"
3989
- };
3984
+ insert(data) {
3985
+ this.operationType = "insert";
3986
+ this.insertData = data;
3987
+ return this;
3990
3988
  }
3991
3989
  /**
3992
- * Upsert (insert or update) rows
3990
+ * Upsert (insert or update) rows (Supabase-compatible)
3991
+ * @param data - Row(s) to upsert
3992
+ * @param options - Upsert options (onConflict, ignoreDuplicates, defaultToNull)
3993
3993
  */
3994
- async upsert(data) {
3994
+ async upsert(data, options) {
3995
3995
  const body = Array.isArray(data) ? data : data;
3996
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body, {
3997
- headers: {
3998
- Prefer: "resolution=merge-duplicates"
3999
- }
4000
- });
3996
+ const preferValues = [];
3997
+ if (options?.ignoreDuplicates) {
3998
+ preferValues.push("resolution=ignore-duplicates");
3999
+ } else {
4000
+ preferValues.push("resolution=merge-duplicates");
4001
+ }
4002
+ if (options?.defaultToNull) {
4003
+ preferValues.push("missing=default");
4004
+ }
4005
+ const headers = {
4006
+ Prefer: preferValues.join(",")
4007
+ };
4008
+ let path = `/api/v1/tables/${this.table}`;
4009
+ if (options?.onConflict) {
4010
+ path += `?on_conflict=${encodeURIComponent(options.onConflict)}`;
4011
+ }
4012
+ const response = await this.fetch.post(path, body, { headers });
4001
4013
  return {
4002
4014
  data: response,
4003
4015
  error: null,
@@ -4009,32 +4021,17 @@ var QueryBuilder = class {
4009
4021
  /**
4010
4022
  * Update rows matching the filters
4011
4023
  */
4012
- async update(data) {
4013
- const queryString = this.buildQueryString();
4014
- const path = `/api/v1/tables/${this.table}${queryString}`;
4015
- const response = await this.fetch.patch(path, data);
4016
- return {
4017
- data: response,
4018
- error: null,
4019
- count: null,
4020
- status: 200,
4021
- statusText: "OK"
4022
- };
4024
+ update(data) {
4025
+ this.operationType = "update";
4026
+ this.updateData = data;
4027
+ return this;
4023
4028
  }
4024
4029
  /**
4025
4030
  * Delete rows matching the filters
4026
4031
  */
4027
- async delete() {
4028
- const queryString = this.buildQueryString();
4029
- const path = `/api/v1/tables/${this.table}${queryString}`;
4030
- await this.fetch.delete(path);
4031
- return {
4032
- data: null,
4033
- error: null,
4034
- count: null,
4035
- status: 204,
4036
- statusText: "No Content"
4037
- };
4032
+ delete() {
4033
+ this.operationType = "delete";
4034
+ return this;
4038
4035
  }
4039
4036
  /**
4040
4037
  * Equal to
@@ -4120,6 +4117,71 @@ var QueryBuilder = class {
4120
4117
  this.filters.push({ column, operator: "fts", value: query });
4121
4118
  return this;
4122
4119
  }
4120
+ /**
4121
+ * Negate a filter condition (Supabase-compatible)
4122
+ * @example not('status', 'eq', 'deleted')
4123
+ * @example not('completed_at', 'is', null)
4124
+ */
4125
+ not(column, operator, value) {
4126
+ this.filters.push({ column, operator: "not", value: `${operator}.${this.formatValue(value)}` });
4127
+ return this;
4128
+ }
4129
+ /**
4130
+ * Apply OR logic to filters (Supabase-compatible)
4131
+ * @example or('status.eq.active,status.eq.pending')
4132
+ * @example or('id.eq.2,name.eq.Han')
4133
+ */
4134
+ or(filters) {
4135
+ this.orFilters.push(filters);
4136
+ return this;
4137
+ }
4138
+ /**
4139
+ * Apply AND logic to filters (Supabase-compatible)
4140
+ * Groups multiple conditions that must all be true
4141
+ * @example and('status.eq.active,verified.eq.true')
4142
+ * @example and('age.gte.18,age.lte.65')
4143
+ */
4144
+ and(filters) {
4145
+ this.andFilters.push(filters);
4146
+ return this;
4147
+ }
4148
+ /**
4149
+ * Match multiple columns with exact values (Supabase-compatible)
4150
+ * Shorthand for multiple .eq() calls
4151
+ * @example match({ id: 1, status: 'active', role: 'admin' })
4152
+ */
4153
+ match(conditions) {
4154
+ for (const [column, value] of Object.entries(conditions)) {
4155
+ this.eq(column, value);
4156
+ }
4157
+ return this;
4158
+ }
4159
+ /**
4160
+ * Generic filter method using PostgREST syntax (Supabase-compatible)
4161
+ * @example filter('name', 'in', '("Han","Yoda")')
4162
+ * @example filter('age', 'gte', '18')
4163
+ */
4164
+ filter(column, operator, value) {
4165
+ this.filters.push({ column, operator, value });
4166
+ return this;
4167
+ }
4168
+ /**
4169
+ * Check if column is contained by value (Supabase-compatible)
4170
+ * For arrays and JSONB
4171
+ * @example containedBy('tags', '["news","update"]')
4172
+ */
4173
+ containedBy(column, value) {
4174
+ this.filters.push({ column, operator: "cd", value });
4175
+ return this;
4176
+ }
4177
+ /**
4178
+ * Check if arrays have common elements (Supabase-compatible)
4179
+ * @example overlaps('tags', '["news","sports"]')
4180
+ */
4181
+ overlaps(column, value) {
4182
+ this.filters.push({ column, operator: "ov", value });
4183
+ return this;
4184
+ }
4123
4185
  /**
4124
4186
  * Order results
4125
4187
  */
@@ -4147,12 +4209,32 @@ var QueryBuilder = class {
4147
4209
  }
4148
4210
  /**
4149
4211
  * Return a single row (adds limit(1))
4212
+ * Errors if no rows found
4150
4213
  */
4151
4214
  single() {
4152
4215
  this.singleRow = true;
4153
4216
  this.limitValue = 1;
4154
4217
  return this;
4155
4218
  }
4219
+ /**
4220
+ * Return a single row or null (adds limit(1))
4221
+ * Does not error if no rows found (Supabase-compatible)
4222
+ * @example
4223
+ * ```typescript
4224
+ * // Returns null instead of erroring when no row exists
4225
+ * const { data, error } = await client
4226
+ * .from('users')
4227
+ * .select('*')
4228
+ * .eq('id', 999)
4229
+ * .maybeSingle()
4230
+ * // data will be null if no row found
4231
+ * ```
4232
+ */
4233
+ maybeSingle() {
4234
+ this.maybeSingleRow = true;
4235
+ this.limitValue = 1;
4236
+ return this;
4237
+ }
4156
4238
  /**
4157
4239
  * Range selection (pagination)
4158
4240
  */
@@ -4328,13 +4410,13 @@ var QueryBuilder = class {
4328
4410
  * { name: 'Alice', email: 'alice@example.com' },
4329
4411
  * { name: 'Bob', email: 'bob@example.com' },
4330
4412
  * { name: 'Charlie', email: 'charlie@example.com' }
4331
- * ]).execute()
4413
+ * ])
4332
4414
  * ```
4333
4415
  *
4334
4416
  * @category Batch Operations
4335
4417
  */
4336
4418
  async insertMany(rows) {
4337
- return this.insert(rows);
4419
+ return this.insert(rows).execute();
4338
4420
  }
4339
4421
  /**
4340
4422
  * Update multiple rows matching the filters (batch update)
@@ -4351,19 +4433,17 @@ var QueryBuilder = class {
4351
4433
  * const { data } = await client.from('products')
4352
4434
  * .eq('category', 'electronics')
4353
4435
  * .updateMany({ discount: 10, updated_at: new Date() })
4354
- * .execute()
4355
4436
  *
4356
4437
  * // Mark all pending orders as processing
4357
4438
  * const { data } = await client.from('orders')
4358
4439
  * .eq('status', 'pending')
4359
4440
  * .updateMany({ status: 'processing' })
4360
- * .execute()
4361
4441
  * ```
4362
4442
  *
4363
4443
  * @category Batch Operations
4364
4444
  */
4365
4445
  async updateMany(data) {
4366
- return this.update(data);
4446
+ return this.update(data).execute();
4367
4447
  }
4368
4448
  /**
4369
4449
  * Delete multiple rows matching the filters (batch delete)
@@ -4379,27 +4459,66 @@ var QueryBuilder = class {
4379
4459
  * await client.from('users')
4380
4460
  * .eq('active', false)
4381
4461
  * .deleteMany()
4382
- * .execute()
4383
4462
  *
4384
4463
  * // Delete old logs
4385
4464
  * await client.from('logs')
4386
4465
  * .lt('created_at', '2024-01-01')
4387
4466
  * .deleteMany()
4388
- * .execute()
4389
4467
  * ```
4390
4468
  *
4391
4469
  * @category Batch Operations
4392
4470
  */
4393
4471
  async deleteMany() {
4394
- return this.delete();
4472
+ return this.delete().execute();
4395
4473
  }
4396
4474
  /**
4397
4475
  * Execute the query and return results
4398
4476
  */
4399
4477
  async execute() {
4400
- const queryString = this.buildQueryString();
4401
- const path = `/api/v1/tables/${this.table}${queryString}`;
4402
4478
  try {
4479
+ if (this.operationType === "insert") {
4480
+ if (!this.insertData) {
4481
+ throw new Error("Insert data is required for insert operation");
4482
+ }
4483
+ const body = Array.isArray(this.insertData) ? this.insertData : this.insertData;
4484
+ const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
4485
+ return {
4486
+ data: response,
4487
+ error: null,
4488
+ count: Array.isArray(this.insertData) ? this.insertData.length : 1,
4489
+ status: 201,
4490
+ statusText: "Created"
4491
+ };
4492
+ }
4493
+ if (this.operationType === "update") {
4494
+ if (!this.updateData) {
4495
+ throw new Error("Update data is required for update operation");
4496
+ }
4497
+ const queryString2 = this.buildQueryString();
4498
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4499
+ const response = await this.fetch.patch(path2, this.updateData);
4500
+ return {
4501
+ data: response,
4502
+ error: null,
4503
+ count: null,
4504
+ status: 200,
4505
+ statusText: "OK"
4506
+ };
4507
+ }
4508
+ if (this.operationType === "delete") {
4509
+ const queryString2 = this.buildQueryString();
4510
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4511
+ await this.fetch.delete(path2);
4512
+ return {
4513
+ data: null,
4514
+ error: null,
4515
+ count: null,
4516
+ status: 204,
4517
+ statusText: "No Content"
4518
+ };
4519
+ }
4520
+ const queryString = this.buildQueryString();
4521
+ const path = `/api/v1/tables/${this.table}${queryString}`;
4403
4522
  const data = await this.fetch.get(path);
4404
4523
  if (this.singleRow) {
4405
4524
  if (Array.isArray(data) && data.length === 0) {
@@ -4420,6 +4539,25 @@ var QueryBuilder = class {
4420
4539
  statusText: "OK"
4421
4540
  };
4422
4541
  }
4542
+ if (this.maybeSingleRow) {
4543
+ if (Array.isArray(data) && data.length === 0) {
4544
+ return {
4545
+ data: null,
4546
+ error: null,
4547
+ count: 0,
4548
+ status: 200,
4549
+ statusText: "OK"
4550
+ };
4551
+ }
4552
+ const singleData = Array.isArray(data) ? data[0] : data;
4553
+ return {
4554
+ data: singleData,
4555
+ error: null,
4556
+ count: 1,
4557
+ status: 200,
4558
+ statusText: "OK"
4559
+ };
4560
+ }
4423
4561
  return {
4424
4562
  data,
4425
4563
  error: null,
@@ -4441,6 +4579,37 @@ var QueryBuilder = class {
4441
4579
  };
4442
4580
  }
4443
4581
  }
4582
+ /**
4583
+ * Execute the query and throw an error if one occurs (Supabase-compatible)
4584
+ * Returns the data directly instead of { data, error } wrapper
4585
+ *
4586
+ * @throws {Error} If the query fails or returns an error
4587
+ * @example
4588
+ * ```typescript
4589
+ * // Throws error instead of returning { data, error }
4590
+ * try {
4591
+ * const user = await client
4592
+ * .from('users')
4593
+ * .select('*')
4594
+ * .eq('id', 1)
4595
+ * .single()
4596
+ * .throwOnError()
4597
+ * } catch (error) {
4598
+ * console.error('Query failed:', error)
4599
+ * }
4600
+ * ```
4601
+ */
4602
+ async throwOnError() {
4603
+ const response = await this.execute();
4604
+ if (response.error) {
4605
+ const error = new Error(response.error.message);
4606
+ if (response.error.code) {
4607
+ error.code = response.error.code;
4608
+ }
4609
+ throw error;
4610
+ }
4611
+ return response.data;
4612
+ }
4444
4613
  /**
4445
4614
  * Make QueryBuilder awaitable (implements PromiseLike)
4446
4615
  * This allows using `await client.from('table').select()` without calling `.execute()`
@@ -4468,6 +4637,12 @@ var QueryBuilder = class {
4468
4637
  for (const filter of this.filters) {
4469
4638
  params.append(filter.column, `${filter.operator}.${this.formatValue(filter.value)}`);
4470
4639
  }
4640
+ for (const orFilter of this.orFilters) {
4641
+ params.append("or", `(${orFilter})`);
4642
+ }
4643
+ for (const andFilter of this.andFilters) {
4644
+ params.append("and", `(${andFilter})`);
4645
+ }
4471
4646
  if (this.groupByColumns && this.groupByColumns.length > 0) {
4472
4647
  params.append("group_by", this.groupByColumns.join(","));
4473
4648
  }