@ragable/sdk 0.8.1 → 0.8.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/dist/index.js CHANGED
@@ -24,6 +24,12 @@ var index_exports = {};
24
24
  __export(index_exports, {
25
25
  AuthBroadcastChannel: () => AuthBroadcastChannel,
26
26
  BrowserStorageBucketClient: () => BrowserStorageBucketClient,
27
+ CollectionDeleteBuilder: () => CollectionDeleteBuilder,
28
+ CollectionInsertChain: () => CollectionInsertChain,
29
+ CollectionMutationReturning: () => CollectionMutationReturning,
30
+ CollectionSelectBuilder: () => CollectionSelectBuilder,
31
+ CollectionUpdateBuilder: () => CollectionUpdateBuilder,
32
+ CollectionUpsertBuilder: () => CollectionUpsertBuilder,
27
33
  CookieStorageAdapter: () => CookieStorageAdapter,
28
34
  DEFAULT_RAGABLE_API_BASE: () => DEFAULT_RAGABLE_API_BASE,
29
35
  LocalStorageAdapter: () => LocalStorageAdapter,
@@ -53,6 +59,7 @@ __export(index_exports, {
53
59
  RagableError: () => RagableError,
54
60
  RagableNetworkError: () => RagableNetworkError,
55
61
  RagableSdkError: () => RagableSdkError,
62
+ RagableServerClient: () => RagableServerClient,
56
63
  RagableTimeoutError: () => RagableTimeoutError,
57
64
  SessionStorageAdapter: () => SessionStorageAdapter,
58
65
  Transport: () => Transport,
@@ -67,6 +74,7 @@ __export(index_exports, {
67
74
  createBrowserClient: () => createBrowserClient,
68
75
  createClient: () => createClient,
69
76
  createRagableBrowserClient: () => createRagableBrowserClient,
77
+ createServerClient: () => createServerClient,
70
78
  createStreamResultFromParts: () => createStreamResultFromParts,
71
79
  detectStorage: () => detectStorage,
72
80
  effectiveDataAuth: () => effectiveDataAuth,
@@ -82,6 +90,8 @@ __export(index_exports, {
82
90
  normalizeBrowserApiBase: () => normalizeBrowserApiBase,
83
91
  parseAgentStreamAgentInfo: () => parseAgentStreamAgentInfo,
84
92
  parseAgentStreamDone: () => parseAgentStreamDone,
93
+ parseOrString: () => parseOrString,
94
+ parseSelectExpression: () => parseSelectExpression,
85
95
  parseSseDataLine: () => parseSseDataLine,
86
96
  parseTransportResponse: () => parseTransportResponse,
87
97
  readSseStream: () => readSseStream,
@@ -1843,6 +1853,824 @@ var PostgrestTableApi = class {
1843
1853
  }
1844
1854
  };
1845
1855
 
1856
+ // src/collection-query.ts
1857
+ function flattenRecord(record) {
1858
+ return {
1859
+ ...record.data,
1860
+ id: record.id,
1861
+ createdAt: record.createdAt,
1862
+ updatedAt: record.updatedAt
1863
+ };
1864
+ }
1865
+ function parseColumns(columns) {
1866
+ const trimmed = (columns ?? "*").trim();
1867
+ if (trimmed.includes("(")) {
1868
+ throw new RagableError(
1869
+ "Embeds and aggregates are only supported in .select() queries, not in a mutation's returning .select(). Use plain column names here.",
1870
+ 400,
1871
+ { code: "SDK_COLLECTION_SELECT_PLAIN_ONLY" }
1872
+ );
1873
+ }
1874
+ if (!trimmed || trimmed === "*") return null;
1875
+ const fields = trimmed.split(",").map((c) => c.trim()).filter(Boolean).filter((c) => c !== "*");
1876
+ return fields.length > 0 ? fields : null;
1877
+ }
1878
+ var IDENT = "[A-Za-z_][A-Za-z0-9_]*";
1879
+ var AGGREGATE_RE = new RegExp(
1880
+ `^(?:(${IDENT}):)?(?:(${IDENT})\\.)?(count|sum|avg|min|max)\\(\\)$`
1881
+ );
1882
+ var EMBED_RE = new RegExp(`^(?:(${IDENT}):)?(${IDENT})(?:!(${IDENT}))?\\(([^()]*)\\)$`);
1883
+ var FIELD_RE = new RegExp(`^${IDENT}$`);
1884
+ function parseSelectExpression(columns) {
1885
+ const trimmed = (columns ?? "*").trim();
1886
+ const result = { fields: null, embeds: [], aggregates: [] };
1887
+ if (!trimmed || trimmed === "*") return result;
1888
+ const fields = [];
1889
+ let sawStar = false;
1890
+ for (const segment of splitTopLevel(trimmed)) {
1891
+ if (segment === "*") {
1892
+ sawStar = true;
1893
+ continue;
1894
+ }
1895
+ const agg = AGGREGATE_RE.exec(segment);
1896
+ if (agg) {
1897
+ const [, alias, field, fn] = agg;
1898
+ if (fn !== "count" && !field) {
1899
+ throw new RagableError(
1900
+ `.select(): "${segment}" \u2014 ${fn}() needs a field, e.g. "amount.${fn}()".`,
1901
+ 400,
1902
+ { code: "SDK_COLLECTION_AGGREGATE_FIELD_REQUIRED" }
1903
+ );
1904
+ }
1905
+ const resolvedAlias = alias ?? (fn === "count" && !field ? "count" : fn);
1906
+ if (result.aggregates.some((a) => a.alias === resolvedAlias)) {
1907
+ throw new RagableError(
1908
+ `.select(): duplicate aggregate key "${resolvedAlias}" \u2014 alias each function, e.g. "total:amount.sum()".`,
1909
+ 400,
1910
+ { code: "SDK_COLLECTION_AGGREGATE_DUPLICATE_ALIAS" }
1911
+ );
1912
+ }
1913
+ result.aggregates.push({
1914
+ fn,
1915
+ ...field ? { field } : {},
1916
+ alias: resolvedAlias
1917
+ });
1918
+ continue;
1919
+ }
1920
+ const embed = EMBED_RE.exec(segment);
1921
+ if (embed) {
1922
+ const [, alias, collection, hint, cols] = embed;
1923
+ const inner = (cols ?? "").trim();
1924
+ const embedColumns = !inner || inner === "*" ? null : inner.split(",").map((c) => c.trim()).filter(Boolean).filter((c) => c !== "*");
1925
+ result.embeds.push({
1926
+ alias: alias ?? collection,
1927
+ collection,
1928
+ ...hint ? { hint } : {},
1929
+ columns: embedColumns && embedColumns.length > 0 ? embedColumns : null
1930
+ });
1931
+ continue;
1932
+ }
1933
+ if (FIELD_RE.test(segment)) {
1934
+ fields.push(segment);
1935
+ continue;
1936
+ }
1937
+ throw new RagableError(
1938
+ `.select(): could not parse "${segment}". Expected a column name, an embed like "author:users!author_id(name)", or an aggregate like "count()" / "total:amount.sum()".`,
1939
+ 400,
1940
+ { code: "SDK_COLLECTION_SELECT_SYNTAX" }
1941
+ );
1942
+ }
1943
+ if (result.aggregates.length > 0) {
1944
+ if (result.embeds.length > 0) {
1945
+ throw new RagableError(
1946
+ ".select(): aggregates and embeds cannot be combined \u2014 run them as separate queries.",
1947
+ 400,
1948
+ { code: "SDK_COLLECTION_AGGREGATE_WITH_EMBED" }
1949
+ );
1950
+ }
1951
+ if (sawStar) {
1952
+ throw new RagableError(
1953
+ '.select(): "*" cannot be combined with aggregates \u2014 list the group-by fields explicitly, e.g. .select("category, count()").',
1954
+ 400,
1955
+ { code: "SDK_COLLECTION_AGGREGATE_WITH_STAR" }
1956
+ );
1957
+ }
1958
+ }
1959
+ result.fields = sawStar || fields.length === 0 ? null : fields;
1960
+ if (result.aggregates.length > 0) result.fields = fields.length > 0 ? fields : null;
1961
+ return result;
1962
+ }
1963
+ function projectRow(row, fields) {
1964
+ if (!fields) return row;
1965
+ const out = {
1966
+ id: row.id,
1967
+ createdAt: row.createdAt,
1968
+ updatedAt: row.updatedAt
1969
+ };
1970
+ for (const field of fields) {
1971
+ if (field in row) out[field] = row[field];
1972
+ }
1973
+ return out;
1974
+ }
1975
+ var FILTER_OPS = /* @__PURE__ */ new Set([
1976
+ "eq",
1977
+ "neq",
1978
+ "gt",
1979
+ "gte",
1980
+ "lt",
1981
+ "lte",
1982
+ "like",
1983
+ "ilike",
1984
+ "startsWith",
1985
+ "endsWith",
1986
+ "in",
1987
+ "nin",
1988
+ "contains",
1989
+ "is",
1990
+ "exists"
1991
+ ]);
1992
+ function splitTopLevel(input) {
1993
+ const parts = [];
1994
+ let depth = 0;
1995
+ let inQuotes = false;
1996
+ let current = "";
1997
+ for (const ch of input) {
1998
+ if (ch === '"') inQuotes = !inQuotes;
1999
+ if (!inQuotes) {
2000
+ if (ch === "(") depth++;
2001
+ else if (ch === ")") depth = Math.max(0, depth - 1);
2002
+ if (ch === "," && depth === 0) {
2003
+ parts.push(current);
2004
+ current = "";
2005
+ continue;
2006
+ }
2007
+ }
2008
+ current += ch;
2009
+ }
2010
+ parts.push(current);
2011
+ return parts.map((p) => p.trim()).filter(Boolean);
2012
+ }
2013
+ function coerceOrValue(raw) {
2014
+ const v = raw.trim();
2015
+ if (v === "true") return true;
2016
+ if (v === "false") return false;
2017
+ if (v === "null") return null;
2018
+ if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
2019
+ if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) return v.slice(1, -1);
2020
+ return v;
2021
+ }
2022
+ function orSyntaxError(segment) {
2023
+ return new RagableError(
2024
+ `.or(): could not parse "${segment}". Expected "field.op.value" segments separated by commas, e.g. .or('done.eq.true,priority.gte.3') or .or('and(a.eq.1,b.eq.2),c.is.null'). Ops: ${[...FILTER_OPS].join(", ")} (prefix with "not." to negate).`,
2025
+ 400,
2026
+ { code: "SDK_COLLECTION_OR_SYNTAX" }
2027
+ );
2028
+ }
2029
+ function mergeCondition(group, field, op, value, negate) {
2030
+ const condition = negate ? { not: { [op]: value } } : { [op]: value };
2031
+ const existing = group[field];
2032
+ if (existing && typeof existing === "object" && !Array.isArray(existing)) {
2033
+ const prev = existing;
2034
+ if (negate && prev.not && typeof prev.not === "object" && !Array.isArray(prev.not)) {
2035
+ group[field] = {
2036
+ ...prev,
2037
+ not: { ...prev.not, [op]: value }
2038
+ };
2039
+ return;
2040
+ }
2041
+ group[field] = { ...prev, ...condition };
2042
+ return;
2043
+ }
2044
+ group[field] = condition;
2045
+ }
2046
+ function parseOrCondition(segment, group) {
2047
+ const firstDot = segment.indexOf(".");
2048
+ if (firstDot <= 0) throw orSyntaxError(segment);
2049
+ const field = segment.slice(0, firstDot);
2050
+ let rest = segment.slice(firstDot + 1);
2051
+ let negate = false;
2052
+ if (rest.startsWith("not.")) {
2053
+ negate = true;
2054
+ rest = rest.slice(4);
2055
+ }
2056
+ const opDot = rest.indexOf(".");
2057
+ if (opDot <= 0) throw orSyntaxError(segment);
2058
+ const op = rest.slice(0, opDot);
2059
+ const rawValue = rest.slice(opDot + 1);
2060
+ if (!FILTER_OPS.has(op)) throw orSyntaxError(segment);
2061
+ let value;
2062
+ if (op === "in" || op === "nin") {
2063
+ const inner = rawValue.replace(/^\(/, "").replace(/\)$/, "");
2064
+ value = inner.trim().length === 0 ? [] : inner.split(",").map(coerceOrValue);
2065
+ } else {
2066
+ value = coerceOrValue(rawValue);
2067
+ }
2068
+ mergeCondition(group, field, op, value, negate);
2069
+ }
2070
+ function parseOrString(input) {
2071
+ const groups = [];
2072
+ for (const segment of splitTopLevel(input)) {
2073
+ if (/^and\(/.test(segment) && segment.endsWith(")")) {
2074
+ const inner = segment.slice(segment.indexOf("(") + 1, -1);
2075
+ const group = {};
2076
+ for (const condition of splitTopLevel(inner)) parseOrCondition(condition, group);
2077
+ if (Object.keys(group).length === 0) throw orSyntaxError(segment);
2078
+ groups.push(group);
2079
+ } else {
2080
+ const group = {};
2081
+ parseOrCondition(segment, group);
2082
+ groups.push(group);
2083
+ }
2084
+ }
2085
+ if (groups.length === 0) throw orSyntaxError(input);
2086
+ return groups;
2087
+ }
2088
+ var CollectionConditionBuilder = class {
2089
+ constructor() {
2090
+ __publicField(this, "filters", []);
2091
+ __publicField(this, "orGroups", null);
2092
+ __publicField(this, "signal");
2093
+ }
2094
+ /** `field = value` (or `IS NULL` when value is null). */
2095
+ eq(field, value) {
2096
+ return this.push(field, "eq", value);
2097
+ }
2098
+ /** `field != value` (null-aware). */
2099
+ neq(field, value) {
2100
+ return this.push(field, "neq", value);
2101
+ }
2102
+ gt(field, value) {
2103
+ return this.push(field, "gt", value);
2104
+ }
2105
+ gte(field, value) {
2106
+ return this.push(field, "gte", value);
2107
+ }
2108
+ lt(field, value) {
2109
+ return this.push(field, "lt", value);
2110
+ }
2111
+ lte(field, value) {
2112
+ return this.push(field, "lte", value);
2113
+ }
2114
+ /** SQL LIKE — you supply the `%` wildcards, e.g. `.like("title", "%report%")`. */
2115
+ like(field, pattern) {
2116
+ return this.push(field, "like", pattern);
2117
+ }
2118
+ /** Case-insensitive LIKE. */
2119
+ ilike(field, pattern) {
2120
+ return this.push(field, "ilike", pattern);
2121
+ }
2122
+ /** Prefix match; wildcard characters in `value` are matched literally. */
2123
+ startsWith(field, value) {
2124
+ return this.push(field, "startsWith", value);
2125
+ }
2126
+ /** Suffix match; wildcard characters in `value` are matched literally. */
2127
+ endsWith(field, value) {
2128
+ return this.push(field, "endsWith", value);
2129
+ }
2130
+ /** Value is one of `values`. */
2131
+ in(field, values) {
2132
+ return this.push(field, "in", values);
2133
+ }
2134
+ /** Value is NOT one of `values`. */
2135
+ nin(field, values) {
2136
+ return this.push(field, "nin", values);
2137
+ }
2138
+ /**
2139
+ * JSONB containment: array fields contain the given element(s), object
2140
+ * fields contain the given key/value pairs.
2141
+ * `.contains("tags", ["urgent"])`, `.contains("meta", { plan: "pro" })`.
2142
+ */
2143
+ contains(field, value) {
2144
+ return this.push(field, "contains", value);
2145
+ }
2146
+ /** Strict null / boolean check: `.is("archivedAt", null)`, `.is("done", true)`. */
2147
+ is(field, value) {
2148
+ return this.push(field, "is", value);
2149
+ }
2150
+ /** Whether the JSON key is present at all: `.exists("avatarUrl", false)`. */
2151
+ exists(field, present = true) {
2152
+ return this.push(field, "exists", present);
2153
+ }
2154
+ /** Negated condition: `.not("status", "eq", "archived")`. */
2155
+ not(field, op, value) {
2156
+ this.assertOp(op);
2157
+ this.filters.push({ field: String(field), op, value, not: true });
2158
+ return this;
2159
+ }
2160
+ /** Generic escape hatch, mirroring Supabase `.filter(column, op, value)`. */
2161
+ filter(field, op, value) {
2162
+ this.assertOp(op);
2163
+ return this.push(field, op, value);
2164
+ }
2165
+ /** Equality on every key of `query` (null values become IS NULL). */
2166
+ match(query) {
2167
+ for (const [field, value] of Object.entries(query)) {
2168
+ this.push(field, "eq", value);
2169
+ }
2170
+ return this;
2171
+ }
2172
+ /**
2173
+ * OR conditions, ANDed with the other filters. Accepts the Supabase string
2174
+ * grammar — `.or('done.eq.true,priority.gte.3')`, with `and(...)` for a
2175
+ * multi-condition branch — or an array of where-style objects:
2176
+ * `.or([{ done: true }, { priority: { gte: 3 } }])`.
2177
+ *
2178
+ * One `.or()` per query: the wire format carries a single disjunction.
2179
+ */
2180
+ or(conditions) {
2181
+ if (this.orGroups) {
2182
+ throw new RagableError(
2183
+ ".or() can only be used once per query. Combine branches in a single call: .or('a.eq.1,b.eq.2').",
2184
+ 400,
2185
+ { code: "SDK_COLLECTION_OR_TWICE" }
2186
+ );
2187
+ }
2188
+ this.orGroups = typeof conditions === "string" ? parseOrString(conditions) : conditions;
2189
+ if (!Array.isArray(this.orGroups) || this.orGroups.length === 0) {
2190
+ throw new RagableError(
2191
+ ".or() requires at least one condition.",
2192
+ 400,
2193
+ { code: "SDK_COLLECTION_OR_EMPTY" }
2194
+ );
2195
+ }
2196
+ return this;
2197
+ }
2198
+ abortSignal(signal) {
2199
+ this.signal = signal;
2200
+ return this;
2201
+ }
2202
+ get hasConditions() {
2203
+ return this.filters.length > 0 || (this.orGroups?.length ?? 0) > 0;
2204
+ }
2205
+ wireFilters() {
2206
+ return this.filters.map((f) => ({
2207
+ field: f.field,
2208
+ op: f.op,
2209
+ value: f.value,
2210
+ ...f.not ? { not: true } : {}
2211
+ }));
2212
+ }
2213
+ assertOp(op) {
2214
+ if (!FILTER_OPS.has(op)) {
2215
+ throw new RagableError(
2216
+ `Unknown filter operator "${op}". Use one of: ${[...FILTER_OPS].join(", ")}.`,
2217
+ 400,
2218
+ { code: "SDK_COLLECTION_BAD_OP" }
2219
+ );
2220
+ }
2221
+ }
2222
+ push(field, op, value) {
2223
+ this.filters.push({ field: String(field), op, value });
2224
+ return this;
2225
+ }
2226
+ };
2227
+ var CollectionSelectBuilder = class extends CollectionConditionBuilder {
2228
+ constructor(request, columns, options) {
2229
+ super();
2230
+ this.request = request;
2231
+ __publicField(this, "_limit");
2232
+ __publicField(this, "_offset");
2233
+ __publicField(this, "_order", []);
2234
+ __publicField(this, "parsed");
2235
+ __publicField(this, "wantCount");
2236
+ __publicField(this, "headOnly");
2237
+ this.parsed = parseSelectExpression(columns);
2238
+ this.wantCount = options?.count === "exact";
2239
+ this.headOnly = options?.head === true;
2240
+ if (this.parsed.aggregates.length > 0 && (this.wantCount || this.headOnly)) {
2241
+ throw new RagableError(
2242
+ 'Aggregate selects compute their own results \u2014 drop { count: "exact" } / { head: true }.',
2243
+ 400,
2244
+ { code: "SDK_COLLECTION_AGGREGATE_WITH_COUNT" }
2245
+ );
2246
+ }
2247
+ }
2248
+ /** Sort by a field. Call again to add secondary sort keys (max 4). */
2249
+ order(field, options) {
2250
+ this._order.push({
2251
+ field: String(field),
2252
+ direction: options?.ascending === true ? "asc" : "desc"
2253
+ });
2254
+ return this;
2255
+ }
2256
+ limit(n) {
2257
+ this._limit = n;
2258
+ return this;
2259
+ }
2260
+ offset(n) {
2261
+ this._offset = n;
2262
+ return this;
2263
+ }
2264
+ /** Rows `from`..`to` inclusive (zero-based), like Supabase `.range()`. */
2265
+ range(from, to) {
2266
+ this._offset = from;
2267
+ this._limit = Math.max(0, to - from + 1);
2268
+ return this;
2269
+ }
2270
+ then(onfulfilled, onrejected) {
2271
+ return this.execute().then(onfulfilled, onrejected);
2272
+ }
2273
+ /**
2274
+ * Exactly one row. 0 or >1 matching rows is an error (code PGRST116),
2275
+ * mirroring Supabase `.single()`.
2276
+ */
2277
+ async single() {
2278
+ const res = await this.executeRows(2);
2279
+ if (res.error) return { data: null, error: res.error };
2280
+ const rows = res.rows;
2281
+ if (rows.length !== 1) {
2282
+ return {
2283
+ data: null,
2284
+ error: new RagableError(
2285
+ "JSON object requested, multiple (or no) rows returned",
2286
+ 406,
2287
+ { code: "PGRST116", details: `The result contains ${rows.length} rows` }
2288
+ )
2289
+ };
2290
+ }
2291
+ return { data: rows[0], error: null };
2292
+ }
2293
+ /** One row or `null` — only >1 matching rows is an error. */
2294
+ async maybeSingle() {
2295
+ const res = await this.executeRows(2);
2296
+ if (res.error) return { data: null, error: res.error };
2297
+ const rows = res.rows;
2298
+ if (rows.length > 1) {
2299
+ return {
2300
+ data: null,
2301
+ error: new RagableError(
2302
+ "JSON object requested, multiple (or no) rows returned",
2303
+ 406,
2304
+ { code: "PGRST116", details: `The result contains ${rows.length} rows` }
2305
+ )
2306
+ };
2307
+ }
2308
+ return { data: rows[0] ?? null, error: null };
2309
+ }
2310
+ buildBody(limitOverride) {
2311
+ const body = {};
2312
+ const filters = this.wireFilters();
2313
+ if (filters.length > 0) body.filters = filters;
2314
+ if (this.orGroups) body.or = this.orGroups;
2315
+ const limit = limitOverride ?? (this.headOnly ? 0 : this._limit);
2316
+ if (limit !== void 0) body.limit = limit;
2317
+ if (this._offset !== void 0 && this._offset > 0) body.offset = this._offset;
2318
+ if (this._order.length === 1) {
2319
+ body.orderBy = this._order[0].field;
2320
+ body.orderDirection = this._order[0].direction;
2321
+ } else if (this._order.length > 1) {
2322
+ body.orderBy = this._order.map((o) => ({
2323
+ field: o.field,
2324
+ direction: o.direction
2325
+ }));
2326
+ }
2327
+ if (this.wantCount) body.count = true;
2328
+ if (this.parsed.embeds.length > 0) {
2329
+ body.embed = this.parsed.embeds.map((e) => ({
2330
+ alias: e.alias,
2331
+ collection: e.collection,
2332
+ ...e.hint ? { hint: e.hint } : {}
2333
+ }));
2334
+ }
2335
+ if (this.parsed.aggregates.length > 0) {
2336
+ body.aggregate = {
2337
+ groupBy: this.parsed.fields ?? [],
2338
+ functions: this.parsed.aggregates.map((a) => ({
2339
+ fn: a.fn,
2340
+ ...a.field ? { field: a.field } : {},
2341
+ alias: a.alias
2342
+ }))
2343
+ };
2344
+ }
2345
+ return body;
2346
+ }
2347
+ /** Flatten the envelope and apply column + embedded-column projection. */
2348
+ shapeRow(record) {
2349
+ const flat = flattenRecord(record);
2350
+ for (const embed of this.parsed.embeds) {
2351
+ if (!embed.columns) continue;
2352
+ const value = flat[embed.alias];
2353
+ if (Array.isArray(value)) {
2354
+ flat[embed.alias] = value.map(
2355
+ (v) => projectEmbeddedObject(v, embed.columns)
2356
+ );
2357
+ } else if (value && typeof value === "object") {
2358
+ flat[embed.alias] = projectEmbeddedObject(value, embed.columns);
2359
+ }
2360
+ }
2361
+ if (!this.parsed.fields) return flat;
2362
+ const out = {
2363
+ id: flat.id,
2364
+ createdAt: flat.createdAt,
2365
+ updatedAt: flat.updatedAt
2366
+ };
2367
+ for (const field of this.parsed.fields) {
2368
+ if (field in flat) out[field] = flat[field];
2369
+ }
2370
+ for (const embed of this.parsed.embeds) {
2371
+ if (embed.alias in flat) out[embed.alias] = flat[embed.alias];
2372
+ }
2373
+ return out;
2374
+ }
2375
+ async executeRows(limitOverride) {
2376
+ if (this.parsed.aggregates.length > 0) {
2377
+ const result2 = await asPostgrestResponse(
2378
+ () => this.request(
2379
+ "POST",
2380
+ "/find",
2381
+ this.buildBody(limitOverride),
2382
+ this.signal
2383
+ )
2384
+ );
2385
+ if (result2.error) return { error: result2.error, rows: null, total: null };
2386
+ return { error: null, rows: result2.data ?? [], total: null };
2387
+ }
2388
+ const result = await asPostgrestResponse(
2389
+ () => this.request(
2390
+ "POST",
2391
+ "/find",
2392
+ this.buildBody(limitOverride),
2393
+ this.signal
2394
+ )
2395
+ );
2396
+ if (result.error) return { error: result.error, rows: null, total: null };
2397
+ const payload = result.data;
2398
+ const records = Array.isArray(payload) ? payload : payload?.records ?? [];
2399
+ const total = Array.isArray(payload) ? null : payload?.total ?? null;
2400
+ const rows = records.map((r) => this.shapeRow(r));
2401
+ return { error: null, rows, total };
2402
+ }
2403
+ async execute() {
2404
+ const res = await this.executeRows();
2405
+ if (res.error) return { data: null, error: res.error, count: null };
2406
+ return {
2407
+ data: this.headOnly ? [] : res.rows,
2408
+ error: null,
2409
+ count: res.total
2410
+ };
2411
+ }
2412
+ };
2413
+ function projectEmbeddedObject(value, columns) {
2414
+ const source = value;
2415
+ const out = {};
2416
+ for (const key of ["id", "createdAt", "updatedAt"]) {
2417
+ if (key in source) out[key] = source[key];
2418
+ }
2419
+ for (const column of columns) {
2420
+ if (column in source) out[column] = source[column];
2421
+ }
2422
+ return out;
2423
+ }
2424
+ var CollectionMutationReturning = class {
2425
+ constructor(run, columns) {
2426
+ this.run = run;
2427
+ this.columns = columns;
2428
+ }
2429
+ then(onfulfilled, onrejected) {
2430
+ return this.executeMany().then(onfulfilled, onrejected);
2431
+ }
2432
+ async executeMany() {
2433
+ const res = await this.run();
2434
+ if (res.error) return { data: null, error: res.error };
2435
+ const rows = (res.data ?? []).map(
2436
+ (r) => projectRow(flattenRecord(r), this.columns)
2437
+ );
2438
+ return { data: rows, error: null };
2439
+ }
2440
+ async single() {
2441
+ const many = await this.executeMany();
2442
+ if (many.error) return { data: null, error: many.error };
2443
+ const rows = many.data ?? [];
2444
+ if (rows.length !== 1) {
2445
+ return {
2446
+ data: null,
2447
+ error: new RagableError(
2448
+ "JSON object requested, multiple (or no) rows returned",
2449
+ 406,
2450
+ { code: "PGRST116", details: `The result contains ${rows.length} rows` }
2451
+ )
2452
+ };
2453
+ }
2454
+ return { data: rows[0], error: null };
2455
+ }
2456
+ async maybeSingle() {
2457
+ const many = await this.executeMany();
2458
+ if (many.error) return { data: null, error: many.error };
2459
+ const rows = many.data ?? [];
2460
+ if (rows.length > 1) {
2461
+ return {
2462
+ data: null,
2463
+ error: new RagableError(
2464
+ "JSON object requested, multiple (or no) rows returned",
2465
+ 406,
2466
+ { code: "PGRST116", details: `The result contains ${rows.length} rows` }
2467
+ )
2468
+ };
2469
+ }
2470
+ return { data: rows[0] ?? null, error: null };
2471
+ }
2472
+ };
2473
+ var CollectionUpdateBuilder = class extends CollectionConditionBuilder {
2474
+ constructor(request, patch) {
2475
+ super();
2476
+ this.request = request;
2477
+ this.patch = patch;
2478
+ __publicField(this, "_limit");
2479
+ }
2480
+ limit(n) {
2481
+ this._limit = n;
2482
+ return this;
2483
+ }
2484
+ select(columns = "*") {
2485
+ return new CollectionMutationReturning(
2486
+ () => this.execute(),
2487
+ parseColumns(columns)
2488
+ );
2489
+ }
2490
+ then(onfulfilled, onrejected) {
2491
+ return this.execute().then(
2492
+ (res) => res.error ? { data: null, error: res.error } : { data: null, error: null }
2493
+ ).then(onfulfilled, onrejected);
2494
+ }
2495
+ async execute() {
2496
+ if (!this.hasConditions) {
2497
+ return {
2498
+ data: null,
2499
+ error: new RagableError(
2500
+ "update() requires at least one filter \u2014 add .eq()/.match()/.or() before awaiting. To update every record, filter on a condition that always holds, e.g. .neq('id', null).",
2501
+ 400,
2502
+ { code: "SDK_COLLECTION_UPDATE_NO_FILTER" }
2503
+ )
2504
+ };
2505
+ }
2506
+ return asPostgrestResponse(
2507
+ () => this.request(
2508
+ "PATCH",
2509
+ "/records",
2510
+ {
2511
+ where: {},
2512
+ filters: this.wireFilters(),
2513
+ ...this.orGroups ? { or: this.orGroups } : {},
2514
+ patch: this.patch,
2515
+ ...this._limit !== void 0 ? { limit: this._limit } : {}
2516
+ },
2517
+ this.signal
2518
+ )
2519
+ );
2520
+ }
2521
+ };
2522
+ var CollectionDeleteBuilder = class extends CollectionConditionBuilder {
2523
+ constructor(request) {
2524
+ super();
2525
+ this.request = request;
2526
+ __publicField(this, "_limit");
2527
+ }
2528
+ limit(n) {
2529
+ this._limit = n;
2530
+ return this;
2531
+ }
2532
+ select(columns = "*") {
2533
+ return new CollectionMutationReturning(
2534
+ () => this.execute(),
2535
+ parseColumns(columns)
2536
+ );
2537
+ }
2538
+ then(onfulfilled, onrejected) {
2539
+ return this.execute().then(
2540
+ (res) => res.error ? { data: null, error: res.error } : { data: null, error: null }
2541
+ ).then(onfulfilled, onrejected);
2542
+ }
2543
+ async execute() {
2544
+ if (!this.hasConditions) {
2545
+ return {
2546
+ data: null,
2547
+ error: new RagableError(
2548
+ "delete() requires at least one filter \u2014 add .eq()/.match()/.or() before awaiting. To clear a collection, filter on a condition that always holds, e.g. .neq('id', null).",
2549
+ 400,
2550
+ { code: "SDK_COLLECTION_DELETE_NO_FILTER" }
2551
+ )
2552
+ };
2553
+ }
2554
+ const res = await asPostgrestResponse(
2555
+ () => this.request(
2556
+ "DELETE",
2557
+ "/records",
2558
+ {
2559
+ where: {},
2560
+ filters: this.wireFilters(),
2561
+ ...this.orGroups ? { or: this.orGroups } : {},
2562
+ ...this._limit !== void 0 ? { limit: this._limit } : {}
2563
+ },
2564
+ this.signal
2565
+ )
2566
+ );
2567
+ if (res.error) return { data: null, error: res.error };
2568
+ return { data: res.data.records ?? [], error: null };
2569
+ }
2570
+ };
2571
+ var CollectionInsertChain = class {
2572
+ constructor(request, rows, single) {
2573
+ this.request = request;
2574
+ this.rows = rows;
2575
+ this.single = single;
2576
+ __publicField(this, "_signal");
2577
+ }
2578
+ abortSignal(signal) {
2579
+ this._signal = signal;
2580
+ return this;
2581
+ }
2582
+ select(columns = "*") {
2583
+ return new CollectionMutationReturning(
2584
+ async () => {
2585
+ const res = await this.executeEnvelopes();
2586
+ if (res.error) return { data: null, error: res.error };
2587
+ return { data: res.data, error: null };
2588
+ },
2589
+ parseColumns(columns)
2590
+ );
2591
+ }
2592
+ then(onfulfilled, onrejected) {
2593
+ return this.executeEnvelopes().then((res) => {
2594
+ if (res.error) return { data: null, error: res.error };
2595
+ const envelopes = res.data;
2596
+ const resolved = this.single ? envelopes[0] ?? null : envelopes;
2597
+ return { data: resolved, error: null };
2598
+ }).then(onfulfilled, onrejected);
2599
+ }
2600
+ async executeEnvelopes() {
2601
+ if (this.rows.length === 0) return { data: [], error: null };
2602
+ if (this.single) {
2603
+ const res = await asPostgrestResponse(
2604
+ () => this.request(
2605
+ "POST",
2606
+ "/records",
2607
+ { data: this.rows[0] },
2608
+ this._signal
2609
+ )
2610
+ );
2611
+ if (res.error) return { data: null, error: res.error };
2612
+ return { data: [res.data], error: null };
2613
+ }
2614
+ return asPostgrestResponse(
2615
+ () => this.request(
2616
+ "POST",
2617
+ "/records/batch",
2618
+ { items: this.rows },
2619
+ this._signal
2620
+ )
2621
+ );
2622
+ }
2623
+ };
2624
+ var CollectionUpsertBuilder = class {
2625
+ constructor(request, rows, options) {
2626
+ this.request = request;
2627
+ this.rows = rows;
2628
+ this.options = options;
2629
+ __publicField(this, "_signal");
2630
+ }
2631
+ abortSignal(signal) {
2632
+ this._signal = signal;
2633
+ return this;
2634
+ }
2635
+ select(columns = "*") {
2636
+ return new CollectionMutationReturning(
2637
+ async () => {
2638
+ const res = await this.execute();
2639
+ if (res.error) return { data: null, error: res.error };
2640
+ return { data: res.data.records, error: null };
2641
+ },
2642
+ parseColumns(columns)
2643
+ );
2644
+ }
2645
+ then(onfulfilled, onrejected) {
2646
+ return this.execute().then((res) => {
2647
+ if (res.error) return { data: null, error: res.error };
2648
+ const { inserted, updated, skipped } = res.data;
2649
+ return { data: { inserted, updated, skipped }, error: null };
2650
+ }).then(onfulfilled, onrejected);
2651
+ }
2652
+ async execute() {
2653
+ if (this.rows.length === 0) {
2654
+ return {
2655
+ data: { records: [], inserted: 0, updated: 0, skipped: 0 },
2656
+ error: null
2657
+ };
2658
+ }
2659
+ return asPostgrestResponse(
2660
+ () => this.request(
2661
+ "POST",
2662
+ "/records/upsert",
2663
+ {
2664
+ items: this.rows,
2665
+ onConflict: this.options.onConflict ?? "id",
2666
+ ...this.options.ignoreDuplicates ? { ignoreDuplicates: true } : {}
2667
+ },
2668
+ this._signal
2669
+ )
2670
+ );
2671
+ }
2672
+ };
2673
+
1846
2674
  // src/auth-storage.ts
1847
2675
  var LocalStorageAdapter = class {
1848
2676
  getItem(key) {
@@ -2137,6 +2965,109 @@ var RagableAuth = class {
2137
2965
  this.emit("SIGNED_OUT", null);
2138
2966
  return { error: null };
2139
2967
  }
2968
+ // ── Password recovery & magic links ────────────────────────────────────────
2969
+ /**
2970
+ * Email a password-recovery link. The link points at `redirectTo` (or the
2971
+ * site's deployed domain when omitted) with `?token_hash=…&type=recovery`
2972
+ * appended. On that page, either:
2973
+ * - call {@link resetPassword} with the token and the new password, or
2974
+ * - call {@link verifyOtp} to sign the user in, then `updateUser({ password })`.
2975
+ *
2976
+ * Always resolves successfully for well-formed requests — whether the email
2977
+ * has an account is never revealed.
2978
+ */
2979
+ async resetPasswordForEmail(email, options) {
2980
+ return asPostgrestResponse(async () => {
2981
+ await this.fetchAuth("/forgot-password", "POST", {
2982
+ email,
2983
+ ...options?.redirectTo ? { redirectTo: options.redirectTo } : {}
2984
+ });
2985
+ return {};
2986
+ });
2987
+ }
2988
+ /**
2989
+ * Email a one-click sign-in (magic) link — passwordless auth. New addresses
2990
+ * get an account automatically unless `shouldCreateUser: false`. The emailed
2991
+ * link carries `?token_hash=…&type=magiclink`; complete sign-in on that page
2992
+ * with {@link verifyOtp}.
2993
+ */
2994
+ async signInWithOtp(params) {
2995
+ return asPostgrestResponse(async () => {
2996
+ await this.fetchAuth("/magic-link", "POST", {
2997
+ email: params.email,
2998
+ ...params.options?.emailRedirectTo ? { redirectTo: params.options.emailRedirectTo } : {},
2999
+ ...params.options?.shouldCreateUser === false ? { createUser: false } : {}
3000
+ });
3001
+ return { user: null, session: null };
3002
+ });
3003
+ }
3004
+ /**
3005
+ * Exchange an emailed `token_hash` for a session. Reads the token from the
3006
+ * URL your recovery / magic-link page receives:
3007
+ *
3008
+ * ```ts
3009
+ * const qs = new URLSearchParams(window.location.search);
3010
+ * const { data, error } = await client.auth.verifyOtp({
3011
+ * token_hash: qs.get("token_hash")!,
3012
+ * type: qs.get("type") as "recovery" | "magiclink",
3013
+ * });
3014
+ * ```
3015
+ *
3016
+ * Emits `SIGNED_IN` (and `PASSWORD_RECOVERY` for recovery tokens — listen
3017
+ * for it to route to your "choose a new password" screen).
3018
+ */
3019
+ async verifyOtp(params) {
3020
+ return asPostgrestResponse(async () => {
3021
+ const token = (params.token_hash ?? params.tokenHash ?? "").trim();
3022
+ if (!token) {
3023
+ throw new RagableError(
3024
+ "verifyOtp requires the token_hash from the emailed link.",
3025
+ 400,
3026
+ { code: "SDK_AUTH_MISSING_TOKEN_HASH" }
3027
+ );
3028
+ }
3029
+ const type = params.type === "email" ? "magiclink" : params.type;
3030
+ const raw = await this.fetchAuth("/verify-token", "POST", { token, type });
3031
+ const session = this.rawToSession(raw);
3032
+ await this.setSessionInternal(session, "SIGNED_IN");
3033
+ if (type === "recovery") this.emit("PASSWORD_RECOVERY", session);
3034
+ return { user: session.user, session };
3035
+ });
3036
+ }
3037
+ /**
3038
+ * One-shot recovery: verify the emailed recovery token, set the new
3039
+ * password, and sign the user in — no intermediate session juggling.
3040
+ *
3041
+ * ```ts
3042
+ * const qs = new URLSearchParams(window.location.search);
3043
+ * const { data, error } = await client.auth.resetPassword({
3044
+ * tokenHash: qs.get("token_hash")!,
3045
+ * newPassword: form.password,
3046
+ * });
3047
+ * ```
3048
+ *
3049
+ * Recovery links are single-use: once the password changes, the same link
3050
+ * is rejected.
3051
+ */
3052
+ async resetPassword(params) {
3053
+ return asPostgrestResponse(async () => {
3054
+ const token = (params.tokenHash ?? params.token_hash ?? "").trim();
3055
+ if (!token) {
3056
+ throw new RagableError(
3057
+ "resetPassword requires the token_hash from the recovery email link.",
3058
+ 400,
3059
+ { code: "SDK_AUTH_MISSING_TOKEN_HASH" }
3060
+ );
3061
+ }
3062
+ const raw = await this.fetchAuth("/reset-password", "POST", {
3063
+ token,
3064
+ password: params.newPassword
3065
+ });
3066
+ const session = this.rawToSession(raw);
3067
+ await this.setSessionInternal(session, "SIGNED_IN");
3068
+ return { user: session.user, session };
3069
+ });
3070
+ }
2140
3071
  async refreshSession(refreshToken) {
2141
3072
  return asPostgrestResponse(async () => {
2142
3073
  const token = refreshToken ?? this.currentSession?.refresh_token;
@@ -3348,6 +4279,22 @@ var RagableBrowserAuthClient = class {
3348
4279
  async signOut(_options) {
3349
4280
  return this.auth.signOut(_options);
3350
4281
  }
4282
+ /** Email a password-recovery link — see {@link RagableAuth.resetPasswordForEmail}. */
4283
+ async resetPasswordForEmail(email, options) {
4284
+ return this.auth.resetPasswordForEmail(email, options);
4285
+ }
4286
+ /** Email a magic sign-in link — see {@link RagableAuth.signInWithOtp}. */
4287
+ async signInWithOtp(params) {
4288
+ return this.auth.signInWithOtp(params);
4289
+ }
4290
+ /** Exchange an emailed token_hash for a session — see {@link RagableAuth.verifyOtp}. */
4291
+ async verifyOtp(params) {
4292
+ return this.auth.verifyOtp(params);
4293
+ }
4294
+ /** Verify a recovery token and set a new password in one call — see {@link RagableAuth.resetPassword}. */
4295
+ async resetPassword(params) {
4296
+ return this.auth.resetPassword(params);
4297
+ }
3351
4298
  async register(body) {
3352
4299
  return this.auth.register(body);
3353
4300
  }
@@ -3399,6 +4346,45 @@ var BrowserCollectionApi = class {
3399
4346
  this.database = database;
3400
4347
  this.name = name;
3401
4348
  this.databaseInstanceId = databaseInstanceId;
4349
+ /** Transport for the chainable builders, bound to this collection. */
4350
+ __publicField(this, "chainRequest", (method, path, body, signal) => this.database._requestCollection(
4351
+ method,
4352
+ `/${encodeURIComponent(this.name)}${path}`,
4353
+ body,
4354
+ this.databaseInstanceId,
4355
+ signal
4356
+ ));
4357
+ /**
4358
+ * Supabase-style chainable query. Rows come back flat (`id`, `createdAt`,
4359
+ * `updatedAt` merged into the document fields).
4360
+ *
4361
+ * ```ts
4362
+ * const { data, error, count } = await collections.todos
4363
+ * .select("*", { count: "exact" })
4364
+ * .eq("done", false)
4365
+ * .or("priority.gte.3,starred.eq.true")
4366
+ * .order("createdAt", { ascending: false })
4367
+ * .range(0, 19);
4368
+ * ```
4369
+ *
4370
+ * Relation embeds (one level): `select("*, author:users!author_id(name), comments(*)")`
4371
+ * — to-one embeds become an object (or null), to-many an array. Aggregations:
4372
+ * `select("category, count(), total:amount.sum()")` groups by the plain
4373
+ * columns; pass a type argument for the result shape:
4374
+ * `select<{ category: string; count: number }>("category, count()")`.
4375
+ */
4376
+ __publicField(this, "select", (columns, options) => new CollectionSelectBuilder(this.chainRequest, columns, options));
4377
+ /**
4378
+ * Insert-or-update matched on `onConflict` (`"id"` by default, or any data
4379
+ * field, e.g. `{ onConflict: "slug" }`). Chain `.select()` for the affected
4380
+ * rows. Match-based (no unique indexes): concurrent upserts of the same new
4381
+ * value can both insert.
4382
+ */
4383
+ __publicField(this, "upsert", (values, options) => new CollectionUpsertBuilder(
4384
+ this.chainRequest,
4385
+ Array.isArray(values) ? values : [values],
4386
+ options ?? {}
4387
+ ));
3402
4388
  __publicField(this, "requestFind", (body) => asPostgrestResponse(
3403
4389
  () => this.database._requestCollection(
3404
4390
  "POST",
@@ -3442,14 +4428,20 @@ var BrowserCollectionApi = class {
3442
4428
  __publicField(this, "findUnique", async (args) => {
3443
4429
  return this.findFirst({ where: args.where });
3444
4430
  });
3445
- __publicField(this, "insert", (data) => asPostgrestResponse(
3446
- () => this.database._requestCollection(
3447
- "POST",
3448
- `/${encodeURIComponent(this.name)}/records`,
3449
- { data },
3450
- this.databaseInstanceId
3451
- )
3452
- ));
4431
+ /**
4432
+ * Insert one row or an array of rows. Chain `.select()` for the inserted
4433
+ * rows in flat shape, or await directly for the record envelope(s)
4434
+ * (back-compat extension; Supabase resolves to null without `.select()`).
4435
+ */
4436
+ __publicField(this, "insert", ((data) => {
4437
+ const isArray = Array.isArray(data);
4438
+ const rows = isArray ? data : [data];
4439
+ return new CollectionInsertChain(
4440
+ this.chainRequest,
4441
+ rows,
4442
+ !isArray
4443
+ );
4444
+ }));
3453
4445
  /**
3454
4446
  * Insert multiple rows in one request (server multi-value `INSERT`, single transaction).
3455
4447
  * Empty **`items`** resolves to an empty array. Max batch size is enforced on the server (500).
@@ -3463,16 +4455,28 @@ var BrowserCollectionApi = class {
3463
4455
  )
3464
4456
  ));
3465
4457
  /**
3466
- * Update rows matching `where` (JSON fields, plus envelope `id` / `createdAt` / `updatedAt`).
4458
+ * Two forms:
4459
+ * - `update(patch)` — Supabase-style chain: add filters, then await.
4460
+ * `update({ done: true }).eq("id", id)` (optionally `.select()`).
4461
+ * - `update(where, patch)` — legacy direct call, returns updated records.
3467
4462
  */
3468
- __publicField(this, "update", (where, patch, options) => asPostgrestResponse(
3469
- () => this.database._requestCollection(
3470
- "PATCH",
3471
- `/${encodeURIComponent(this.name)}/records`,
3472
- { where, patch, ...options?.limit ? { limit: options.limit } : {} },
3473
- this.databaseInstanceId
3474
- )
3475
- ));
4463
+ __publicField(this, "update", ((...args) => {
4464
+ if (args.length >= 2) {
4465
+ const [where, patch, options] = args;
4466
+ return asPostgrestResponse(
4467
+ () => this.database._requestCollection(
4468
+ "PATCH",
4469
+ `/${encodeURIComponent(this.name)}/records`,
4470
+ { where, patch, ...options?.limit ? { limit: options.limit } : {} },
4471
+ this.databaseInstanceId
4472
+ )
4473
+ );
4474
+ }
4475
+ return new CollectionUpdateBuilder(
4476
+ this.chainRequest,
4477
+ args[0]
4478
+ );
4479
+ }));
3476
4480
  /**
3477
4481
  * Like {@link BrowserCollectionApi.update} but the success payload includes
3478
4482
  * `meta.count` (number of rows returned from the update, bounded by `limit`).
@@ -3482,14 +4486,26 @@ var BrowserCollectionApi = class {
3482
4486
  if (r.error) return r;
3483
4487
  return { data: { records: r.data, meta: { count: r.data.length } }, error: null };
3484
4488
  });
3485
- __publicField(this, "delete", (where, options) => asPostgrestResponse(
3486
- () => this.database._requestCollection(
3487
- "DELETE",
3488
- `/${encodeURIComponent(this.name)}/records`,
3489
- { where, ...options?.limit ? { limit: options.limit } : {} },
3490
- this.databaseInstanceId
3491
- )
3492
- ));
4489
+ /**
4490
+ * Two forms:
4491
+ * - `delete()` — Supabase-style chain: add filters, then await.
4492
+ * `delete().eq("id", id)` (optionally `.select()`).
4493
+ * - `delete(where)` legacy direct call, returns `{ deleted, records }`.
4494
+ */
4495
+ __publicField(this, "delete", ((...args) => {
4496
+ if (args.length === 0) {
4497
+ return new CollectionDeleteBuilder(this.chainRequest);
4498
+ }
4499
+ const [where, options] = args;
4500
+ return asPostgrestResponse(
4501
+ () => this.database._requestCollection(
4502
+ "DELETE",
4503
+ `/${encodeURIComponent(this.name)}/records`,
4504
+ { where, ...options?.limit ? { limit: options.limit } : {} },
4505
+ this.databaseInstanceId
4506
+ )
4507
+ );
4508
+ }));
3493
4509
  /**
3494
4510
  * Like {@link BrowserCollectionApi.delete} but the success payload includes **`meta.count`**
3495
4511
  * (number of deleted rows), matching {@link BrowserCollectionApi.updateMany}.
@@ -3691,7 +4707,7 @@ var RagableBrowserDatabaseClient = class {
3691
4707
  toUrl(path) {
3692
4708
  return `${normalizeBrowserApiBase()}${path.startsWith("/") ? path : `/${path}`}`;
3693
4709
  }
3694
- async _requestCollection(method, path, body, databaseInstanceId) {
4710
+ async _requestCollection(method, path, body, databaseInstanceId, signal) {
3695
4711
  const gid = requireAuthGroupId(this.options);
3696
4712
  const token = await resolveDatabaseAuthBearer(this.options, this.ragableAuth);
3697
4713
  const id = databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
@@ -3711,7 +4727,8 @@ var RagableBrowserDatabaseClient = class {
3711
4727
  {
3712
4728
  method,
3713
4729
  headers,
3714
- body: body !== void 0 ? JSON.stringify(body) : void 0
4730
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
4731
+ ...signal ? { signal } : {}
3715
4732
  }
3716
4733
  );
3717
4734
  const payload = await parseMaybeJsonBody(response);
@@ -4650,6 +5667,91 @@ function createBrowserClient(options) {
4650
5667
  }
4651
5668
  var createRagableBrowserClient = createBrowserClient;
4652
5669
 
5670
+ // src/server.ts
5671
+ function optionsForPrivilege(config, privilege) {
5672
+ const base = {
5673
+ organizationId: config.organizationId,
5674
+ ...config.websiteId !== void 0 ? { websiteId: config.websiteId } : {},
5675
+ ...config.authGroupId !== void 0 ? { authGroupId: config.authGroupId } : {},
5676
+ ...config.databaseInstanceId !== void 0 ? { databaseInstanceId: config.databaseInstanceId } : {},
5677
+ ...config.fetch !== void 0 ? { fetch: config.fetch } : {},
5678
+ ...config.headers !== void 0 ? { headers: config.headers } : {}
5679
+ };
5680
+ if (privilege === "admin") {
5681
+ const key = config.adminKey?.trim();
5682
+ if (!key) {
5683
+ throw new RagableError(
5684
+ "Admin privilege requires `adminKey` (the auth group's data-admin key). It is not available \u2014 admin functions may be disabled for this project.",
5685
+ 403,
5686
+ { code: "SDK_ADMIN_KEY_REQUIRED" }
5687
+ );
5688
+ }
5689
+ return { ...base, dataAuth: "admin", dataStaticKey: key };
5690
+ }
5691
+ return {
5692
+ ...base,
5693
+ dataAuth: "auto",
5694
+ getAccessToken: () => config.callerToken ?? null,
5695
+ ...config.publicAnonKey !== void 0 ? { dataStaticKey: config.publicAnonKey } : {}
5696
+ };
5697
+ }
5698
+ var RagableServerClient = class _RagableServerClient {
5699
+ constructor(config, privilege) {
5700
+ this.config = config;
5701
+ __publicField(this, "database");
5702
+ __publicField(this, "db");
5703
+ __publicField(this, "storage");
5704
+ __publicField(this, "mail");
5705
+ __publicField(this, "functions");
5706
+ __publicField(this, "ai");
5707
+ __publicField(this, "agents");
5708
+ /** Which credential this client is using. */
5709
+ __publicField(this, "privilege");
5710
+ this.privilege = privilege;
5711
+ const options = optionsForPrivilege(config, privilege);
5712
+ const transport = new Transport({
5713
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
5714
+ ...options.headers !== void 0 ? { headers: options.headers } : {},
5715
+ ...options.transport
5716
+ });
5717
+ this.database = new RagableBrowserDatabaseClient(options, null);
5718
+ this.database._setTransport(transport);
5719
+ this.db = this.database;
5720
+ this.storage = new RagableBrowserStorageClient(options, bindFetch(options.fetch), null);
5721
+ this.mail = new RagableBrowserMailClient(options, null);
5722
+ this.functions = new RagableBrowserFunctionsClient(options, null).asInvoker();
5723
+ this.ai = new RagableBrowserAiClient({
5724
+ organizationId: options.organizationId,
5725
+ ...options.websiteId !== void 0 ? { websiteId: options.websiteId } : {},
5726
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
5727
+ ...options.headers !== void 0 ? { headers: options.headers } : {},
5728
+ apiBase: normalizeBrowserApiBase()
5729
+ });
5730
+ this.agents = new RagableBrowserAgentsClient(options);
5731
+ }
5732
+ /**
5733
+ * A client authenticated with the **data-admin key** — bypasses collection
5734
+ * security (owner/group/claim grants do not apply) and can write protected
5735
+ * collections. Throws `SDK_ADMIN_KEY_REQUIRED` when no admin key is configured.
5736
+ */
5737
+ asAdmin() {
5738
+ return new _RagableServerClient(this.config, "admin");
5739
+ }
5740
+ /**
5741
+ * A client scoped to the invoking end user's identity (respects collection
5742
+ * security). This is already the default; use it to drop back from `asAdmin()`.
5743
+ */
5744
+ asCaller() {
5745
+ return new _RagableServerClient(this.config, "caller");
5746
+ }
5747
+ };
5748
+ function createServerClient(config) {
5749
+ return new RagableServerClient(
5750
+ config,
5751
+ config.defaultPrivilege ?? "caller"
5752
+ );
5753
+ }
5754
+
4653
5755
  // src/index.ts
4654
5756
  function createClient(options) {
4655
5757
  return createBrowserClient(options);
@@ -4658,6 +5760,12 @@ function createClient(options) {
4658
5760
  0 && (module.exports = {
4659
5761
  AuthBroadcastChannel,
4660
5762
  BrowserStorageBucketClient,
5763
+ CollectionDeleteBuilder,
5764
+ CollectionInsertChain,
5765
+ CollectionMutationReturning,
5766
+ CollectionSelectBuilder,
5767
+ CollectionUpdateBuilder,
5768
+ CollectionUpsertBuilder,
4661
5769
  CookieStorageAdapter,
4662
5770
  DEFAULT_RAGABLE_API_BASE,
4663
5771
  LocalStorageAdapter,
@@ -4687,6 +5795,7 @@ function createClient(options) {
4687
5795
  RagableError,
4688
5796
  RagableNetworkError,
4689
5797
  RagableSdkError,
5798
+ RagableServerClient,
4690
5799
  RagableTimeoutError,
4691
5800
  SessionStorageAdapter,
4692
5801
  Transport,
@@ -4701,6 +5810,7 @@ function createClient(options) {
4701
5810
  createBrowserClient,
4702
5811
  createClient,
4703
5812
  createRagableBrowserClient,
5813
+ createServerClient,
4704
5814
  createStreamResultFromParts,
4705
5815
  detectStorage,
4706
5816
  effectiveDataAuth,
@@ -4716,6 +5826,8 @@ function createClient(options) {
4716
5826
  normalizeBrowserApiBase,
4717
5827
  parseAgentStreamAgentInfo,
4718
5828
  parseAgentStreamDone,
5829
+ parseOrString,
5830
+ parseSelectExpression,
4719
5831
  parseSseDataLine,
4720
5832
  parseTransportResponse,
4721
5833
  readSseStream,
@@ -4728,4 +5840,3 @@ function createClient(options) {
4728
5840
  unwrapPostgrest,
4729
5841
  wrapStreamTextAsObject
4730
5842
  });
4731
- //# sourceMappingURL=index.js.map