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

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
@@ -3961,8 +3961,11 @@ var QueryBuilder = class {
3961
3961
  constructor(fetch2, table) {
3962
3962
  this.selectQuery = "*";
3963
3963
  this.filters = [];
3964
+ this.orFilters = [];
3965
+ this.andFilters = [];
3964
3966
  this.orderBys = [];
3965
3967
  this.singleRow = false;
3968
+ this.maybeSingleRow = false;
3966
3969
  this.fetch = fetch2;
3967
3970
  this.table = table;
3968
3971
  }
@@ -3991,15 +3994,29 @@ var QueryBuilder = class {
3991
3994
  };
3992
3995
  }
3993
3996
  /**
3994
- * Upsert (insert or update) rows
3997
+ * Upsert (insert or update) rows (Supabase-compatible)
3998
+ * @param data - Row(s) to upsert
3999
+ * @param options - Upsert options (onConflict, ignoreDuplicates, defaultToNull)
3995
4000
  */
3996
- async upsert(data) {
4001
+ async upsert(data, options) {
3997
4002
  const body = Array.isArray(data) ? data : data;
3998
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body, {
3999
- headers: {
4000
- Prefer: "resolution=merge-duplicates"
4001
- }
4002
- });
4003
+ const preferValues = [];
4004
+ if (options?.ignoreDuplicates) {
4005
+ preferValues.push("resolution=ignore-duplicates");
4006
+ } else {
4007
+ preferValues.push("resolution=merge-duplicates");
4008
+ }
4009
+ if (options?.defaultToNull) {
4010
+ preferValues.push("missing=default");
4011
+ }
4012
+ const headers = {
4013
+ Prefer: preferValues.join(",")
4014
+ };
4015
+ let path = `/api/v1/tables/${this.table}`;
4016
+ if (options?.onConflict) {
4017
+ path += `?on_conflict=${encodeURIComponent(options.onConflict)}`;
4018
+ }
4019
+ const response = await this.fetch.post(path, body, { headers });
4003
4020
  return {
4004
4021
  data: response,
4005
4022
  error: null,
@@ -4122,6 +4139,71 @@ var QueryBuilder = class {
4122
4139
  this.filters.push({ column, operator: "fts", value: query });
4123
4140
  return this;
4124
4141
  }
4142
+ /**
4143
+ * Negate a filter condition (Supabase-compatible)
4144
+ * @example not('status', 'eq', 'deleted')
4145
+ * @example not('completed_at', 'is', null)
4146
+ */
4147
+ not(column, operator, value) {
4148
+ this.filters.push({ column, operator: "not", value: `${operator}.${this.formatValue(value)}` });
4149
+ return this;
4150
+ }
4151
+ /**
4152
+ * Apply OR logic to filters (Supabase-compatible)
4153
+ * @example or('status.eq.active,status.eq.pending')
4154
+ * @example or('id.eq.2,name.eq.Han')
4155
+ */
4156
+ or(filters) {
4157
+ this.orFilters.push(filters);
4158
+ return this;
4159
+ }
4160
+ /**
4161
+ * Apply AND logic to filters (Supabase-compatible)
4162
+ * Groups multiple conditions that must all be true
4163
+ * @example and('status.eq.active,verified.eq.true')
4164
+ * @example and('age.gte.18,age.lte.65')
4165
+ */
4166
+ and(filters) {
4167
+ this.andFilters.push(filters);
4168
+ return this;
4169
+ }
4170
+ /**
4171
+ * Match multiple columns with exact values (Supabase-compatible)
4172
+ * Shorthand for multiple .eq() calls
4173
+ * @example match({ id: 1, status: 'active', role: 'admin' })
4174
+ */
4175
+ match(conditions) {
4176
+ for (const [column, value] of Object.entries(conditions)) {
4177
+ this.eq(column, value);
4178
+ }
4179
+ return this;
4180
+ }
4181
+ /**
4182
+ * Generic filter method using PostgREST syntax (Supabase-compatible)
4183
+ * @example filter('name', 'in', '("Han","Yoda")')
4184
+ * @example filter('age', 'gte', '18')
4185
+ */
4186
+ filter(column, operator, value) {
4187
+ this.filters.push({ column, operator, value });
4188
+ return this;
4189
+ }
4190
+ /**
4191
+ * Check if column is contained by value (Supabase-compatible)
4192
+ * For arrays and JSONB
4193
+ * @example containedBy('tags', '["news","update"]')
4194
+ */
4195
+ containedBy(column, value) {
4196
+ this.filters.push({ column, operator: "cd", value });
4197
+ return this;
4198
+ }
4199
+ /**
4200
+ * Check if arrays have common elements (Supabase-compatible)
4201
+ * @example overlaps('tags', '["news","sports"]')
4202
+ */
4203
+ overlaps(column, value) {
4204
+ this.filters.push({ column, operator: "ov", value });
4205
+ return this;
4206
+ }
4125
4207
  /**
4126
4208
  * Order results
4127
4209
  */
@@ -4149,12 +4231,32 @@ var QueryBuilder = class {
4149
4231
  }
4150
4232
  /**
4151
4233
  * Return a single row (adds limit(1))
4234
+ * Errors if no rows found
4152
4235
  */
4153
4236
  single() {
4154
4237
  this.singleRow = true;
4155
4238
  this.limitValue = 1;
4156
4239
  return this;
4157
4240
  }
4241
+ /**
4242
+ * Return a single row or null (adds limit(1))
4243
+ * Does not error if no rows found (Supabase-compatible)
4244
+ * @example
4245
+ * ```typescript
4246
+ * // Returns null instead of erroring when no row exists
4247
+ * const { data, error } = await client
4248
+ * .from('users')
4249
+ * .select('*')
4250
+ * .eq('id', 999)
4251
+ * .maybeSingle()
4252
+ * // data will be null if no row found
4253
+ * ```
4254
+ */
4255
+ maybeSingle() {
4256
+ this.maybeSingleRow = true;
4257
+ this.limitValue = 1;
4258
+ return this;
4259
+ }
4158
4260
  /**
4159
4261
  * Range selection (pagination)
4160
4262
  */
@@ -4422,6 +4524,25 @@ var QueryBuilder = class {
4422
4524
  statusText: "OK"
4423
4525
  };
4424
4526
  }
4527
+ if (this.maybeSingleRow) {
4528
+ if (Array.isArray(data) && data.length === 0) {
4529
+ return {
4530
+ data: null,
4531
+ error: null,
4532
+ count: 0,
4533
+ status: 200,
4534
+ statusText: "OK"
4535
+ };
4536
+ }
4537
+ const singleData = Array.isArray(data) ? data[0] : data;
4538
+ return {
4539
+ data: singleData,
4540
+ error: null,
4541
+ count: 1,
4542
+ status: 200,
4543
+ statusText: "OK"
4544
+ };
4545
+ }
4425
4546
  return {
4426
4547
  data,
4427
4548
  error: null,
@@ -4443,6 +4564,37 @@ var QueryBuilder = class {
4443
4564
  };
4444
4565
  }
4445
4566
  }
4567
+ /**
4568
+ * Execute the query and throw an error if one occurs (Supabase-compatible)
4569
+ * Returns the data directly instead of { data, error } wrapper
4570
+ *
4571
+ * @throws {Error} If the query fails or returns an error
4572
+ * @example
4573
+ * ```typescript
4574
+ * // Throws error instead of returning { data, error }
4575
+ * try {
4576
+ * const user = await client
4577
+ * .from('users')
4578
+ * .select('*')
4579
+ * .eq('id', 1)
4580
+ * .single()
4581
+ * .throwOnError()
4582
+ * } catch (error) {
4583
+ * console.error('Query failed:', error)
4584
+ * }
4585
+ * ```
4586
+ */
4587
+ async throwOnError() {
4588
+ const response = await this.execute();
4589
+ if (response.error) {
4590
+ const error = new Error(response.error.message);
4591
+ if (response.error.code) {
4592
+ error.code = response.error.code;
4593
+ }
4594
+ throw error;
4595
+ }
4596
+ return response.data;
4597
+ }
4446
4598
  /**
4447
4599
  * Make QueryBuilder awaitable (implements PromiseLike)
4448
4600
  * This allows using `await client.from('table').select()` without calling `.execute()`
@@ -4470,6 +4622,12 @@ var QueryBuilder = class {
4470
4622
  for (const filter of this.filters) {
4471
4623
  params.append(filter.column, `${filter.operator}.${this.formatValue(filter.value)}`);
4472
4624
  }
4625
+ for (const orFilter of this.orFilters) {
4626
+ params.append("or", `(${orFilter})`);
4627
+ }
4628
+ for (const andFilter of this.andFilters) {
4629
+ params.append("and", `(${andFilter})`);
4630
+ }
4473
4631
  if (this.groupByColumns && this.groupByColumns.length > 0) {
4474
4632
  params.append("group_by", this.groupByColumns.join(","));
4475
4633
  }