@ragable/sdk 0.8.2 → 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.d.mts +437 -14
- package/dist/index.d.ts +437 -14
- package/dist/index.js +1050 -28
- package/dist/index.mjs +1042 -28
- package/package.json +1 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
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,
|
|
@@ -84,6 +90,8 @@ __export(index_exports, {
|
|
|
84
90
|
normalizeBrowserApiBase: () => normalizeBrowserApiBase,
|
|
85
91
|
parseAgentStreamAgentInfo: () => parseAgentStreamAgentInfo,
|
|
86
92
|
parseAgentStreamDone: () => parseAgentStreamDone,
|
|
93
|
+
parseOrString: () => parseOrString,
|
|
94
|
+
parseSelectExpression: () => parseSelectExpression,
|
|
87
95
|
parseSseDataLine: () => parseSseDataLine,
|
|
88
96
|
parseTransportResponse: () => parseTransportResponse,
|
|
89
97
|
readSseStream: () => readSseStream,
|
|
@@ -1845,6 +1853,824 @@ var PostgrestTableApi = class {
|
|
|
1845
1853
|
}
|
|
1846
1854
|
};
|
|
1847
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
|
+
|
|
1848
2674
|
// src/auth-storage.ts
|
|
1849
2675
|
var LocalStorageAdapter = class {
|
|
1850
2676
|
getItem(key) {
|
|
@@ -2139,6 +2965,109 @@ var RagableAuth = class {
|
|
|
2139
2965
|
this.emit("SIGNED_OUT", null);
|
|
2140
2966
|
return { error: null };
|
|
2141
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
|
+
}
|
|
2142
3071
|
async refreshSession(refreshToken) {
|
|
2143
3072
|
return asPostgrestResponse(async () => {
|
|
2144
3073
|
const token = refreshToken ?? this.currentSession?.refresh_token;
|
|
@@ -3350,6 +4279,22 @@ var RagableBrowserAuthClient = class {
|
|
|
3350
4279
|
async signOut(_options) {
|
|
3351
4280
|
return this.auth.signOut(_options);
|
|
3352
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
|
+
}
|
|
3353
4298
|
async register(body) {
|
|
3354
4299
|
return this.auth.register(body);
|
|
3355
4300
|
}
|
|
@@ -3401,6 +4346,45 @@ var BrowserCollectionApi = class {
|
|
|
3401
4346
|
this.database = database;
|
|
3402
4347
|
this.name = name;
|
|
3403
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
|
+
));
|
|
3404
4388
|
__publicField(this, "requestFind", (body) => asPostgrestResponse(
|
|
3405
4389
|
() => this.database._requestCollection(
|
|
3406
4390
|
"POST",
|
|
@@ -3444,14 +4428,20 @@ var BrowserCollectionApi = class {
|
|
|
3444
4428
|
__publicField(this, "findUnique", async (args) => {
|
|
3445
4429
|
return this.findFirst({ where: args.where });
|
|
3446
4430
|
});
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
)
|
|
3454
|
-
|
|
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
|
+
}));
|
|
3455
4445
|
/**
|
|
3456
4446
|
* Insert multiple rows in one request (server multi-value `INSERT`, single transaction).
|
|
3457
4447
|
* Empty **`items`** resolves to an empty array. Max batch size is enforced on the server (500).
|
|
@@ -3465,16 +4455,28 @@ var BrowserCollectionApi = class {
|
|
|
3465
4455
|
)
|
|
3466
4456
|
));
|
|
3467
4457
|
/**
|
|
3468
|
-
*
|
|
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.
|
|
3469
4462
|
*/
|
|
3470
|
-
__publicField(this, "update", (
|
|
3471
|
-
()
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
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
|
+
}));
|
|
3478
4480
|
/**
|
|
3479
4481
|
* Like {@link BrowserCollectionApi.update} but the success payload includes
|
|
3480
4482
|
* `meta.count` (number of rows returned from the update, bounded by `limit`).
|
|
@@ -3484,14 +4486,26 @@ var BrowserCollectionApi = class {
|
|
|
3484
4486
|
if (r.error) return r;
|
|
3485
4487
|
return { data: { records: r.data, meta: { count: r.data.length } }, error: null };
|
|
3486
4488
|
});
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
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
|
+
}));
|
|
3495
4509
|
/**
|
|
3496
4510
|
* Like {@link BrowserCollectionApi.delete} but the success payload includes **`meta.count`**
|
|
3497
4511
|
* (number of deleted rows), matching {@link BrowserCollectionApi.updateMany}.
|
|
@@ -3693,7 +4707,7 @@ var RagableBrowserDatabaseClient = class {
|
|
|
3693
4707
|
toUrl(path) {
|
|
3694
4708
|
return `${normalizeBrowserApiBase()}${path.startsWith("/") ? path : `/${path}`}`;
|
|
3695
4709
|
}
|
|
3696
|
-
async _requestCollection(method, path, body, databaseInstanceId) {
|
|
4710
|
+
async _requestCollection(method, path, body, databaseInstanceId, signal) {
|
|
3697
4711
|
const gid = requireAuthGroupId(this.options);
|
|
3698
4712
|
const token = await resolveDatabaseAuthBearer(this.options, this.ragableAuth);
|
|
3699
4713
|
const id = databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
|
|
@@ -3713,7 +4727,8 @@ var RagableBrowserDatabaseClient = class {
|
|
|
3713
4727
|
{
|
|
3714
4728
|
method,
|
|
3715
4729
|
headers,
|
|
3716
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
4730
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
4731
|
+
...signal ? { signal } : {}
|
|
3717
4732
|
}
|
|
3718
4733
|
);
|
|
3719
4734
|
const payload = await parseMaybeJsonBody(response);
|
|
@@ -4745,6 +5760,12 @@ function createClient(options) {
|
|
|
4745
5760
|
0 && (module.exports = {
|
|
4746
5761
|
AuthBroadcastChannel,
|
|
4747
5762
|
BrowserStorageBucketClient,
|
|
5763
|
+
CollectionDeleteBuilder,
|
|
5764
|
+
CollectionInsertChain,
|
|
5765
|
+
CollectionMutationReturning,
|
|
5766
|
+
CollectionSelectBuilder,
|
|
5767
|
+
CollectionUpdateBuilder,
|
|
5768
|
+
CollectionUpsertBuilder,
|
|
4748
5769
|
CookieStorageAdapter,
|
|
4749
5770
|
DEFAULT_RAGABLE_API_BASE,
|
|
4750
5771
|
LocalStorageAdapter,
|
|
@@ -4805,6 +5826,8 @@ function createClient(options) {
|
|
|
4805
5826
|
normalizeBrowserApiBase,
|
|
4806
5827
|
parseAgentStreamAgentInfo,
|
|
4807
5828
|
parseAgentStreamDone,
|
|
5829
|
+
parseOrString,
|
|
5830
|
+
parseSelectExpression,
|
|
4808
5831
|
parseSseDataLine,
|
|
4809
5832
|
parseTransportResponse,
|
|
4810
5833
|
readSseStream,
|
|
@@ -4817,4 +5840,3 @@ function createClient(options) {
|
|
|
4817
5840
|
unwrapPostgrest,
|
|
4818
5841
|
wrapStreamTextAsObject
|
|
4819
5842
|
});
|
|
4820
|
-
//# sourceMappingURL=index.js.map
|