cloudflare-next-intl 0.8.4 → 0.8.6

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.
@@ -14,6 +14,13 @@
14
14
  * load through dynamic `import()` inside these functions, so an app that
15
15
  * never calls a `db` export never bundles any of them.
16
16
  *
17
+ * When `db.supabase.rawSql` is `false` (or `cfni_exec` can't be installed),
18
+ * `withPublicDb`/`withUserDb` throw instead of running in Supabase mode; use
19
+ * `supabaseSelect`/`supabaseInsert`/`supabaseUpsert`/`supabaseUpdate`/
20
+ * `supabaseDelete`/`supabaseRpc` (and their `*AsUser` counterparts) instead —
21
+ * they call `@supabase/supabase-js`'s `.from()`/`.rpc()` API directly, no
22
+ * `cfni_exec`, no raw SQL, only what PostgREST's REST API itself supports.
23
+ *
17
24
  * Generic Drizzle SQL helpers (`excluded`, `onConflictSet`, `ago`, …) live in
18
25
  * the separate `cloudflare-next-intl/dbHelpers` entry point.
19
26
  */
@@ -21,3 +28,5 @@ export { withPublicDb, withUserDb } from './context';
21
28
  export type { DrizzleDb } from './context';
22
29
  export { default as connectToPostgres, disconnectPostgres, resetConnectionState } from './connection';
23
30
  export type { DbRoutingConfig } from '../types/types';
31
+ export { supabaseSelect, supabaseSelectAsUser, supabaseInsert, supabaseInsertAsUser, supabaseUpsert, supabaseUpsertAsUser, supabaseUpdate, supabaseUpdateAsUser, supabaseDelete, supabaseDeleteAsUser, supabaseRpc, supabaseRpcAsUser, } from './supabase_rest';
32
+ export type { SupabaseWhere, SupabaseFilterOperator, SupabaseFilterValue, SupabaseOrderBy, SupabaseTextSearch, SupabaseSelectOptions, SupabaseMutationOptions, SupabaseUpsertOptions, SupabaseResult, } from './supabase_rest';
@@ -14,8 +14,16 @@
14
14
  * load through dynamic `import()` inside these functions, so an app that
15
15
  * never calls a `db` export never bundles any of them.
16
16
  *
17
+ * When `db.supabase.rawSql` is `false` (or `cfni_exec` can't be installed),
18
+ * `withPublicDb`/`withUserDb` throw instead of running in Supabase mode; use
19
+ * `supabaseSelect`/`supabaseInsert`/`supabaseUpsert`/`supabaseUpdate`/
20
+ * `supabaseDelete`/`supabaseRpc` (and their `*AsUser` counterparts) instead —
21
+ * they call `@supabase/supabase-js`'s `.from()`/`.rpc()` API directly, no
22
+ * `cfni_exec`, no raw SQL, only what PostgREST's REST API itself supports.
23
+ *
17
24
  * Generic Drizzle SQL helpers (`excluded`, `onConflictSet`, `ago`, …) live in
18
25
  * the separate `cloudflare-next-intl/dbHelpers` entry point.
19
26
  */
20
27
  export { withPublicDb, withUserDb } from './context';
21
28
  export { default as connectToPostgres, disconnectPostgres, resetConnectionState } from './connection';
29
+ export { supabaseSelect, supabaseSelectAsUser, supabaseInsert, supabaseInsertAsUser, supabaseUpsert, supabaseUpsertAsUser, supabaseUpdate, supabaseUpdateAsUser, supabaseDelete, supabaseDeleteAsUser, supabaseRpc, supabaseRpcAsUser, } from './supabase_rest';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Substitutes `$1`, `$2`, … placeholders in a generated statement with
3
+ * literal-encoded values, so a single-statement RPC call
4
+ * (`cfni_exec(statement, params default '[]')`) never needs to bind params
5
+ * itself — `EXECUTE ... USING` cannot bind more than one differently-typed
6
+ * value the way a real driver does.
7
+ *
8
+ * Placeholders inside single-quoted strings, `E'...'` escape strings,
9
+ * dollar-quoted bodies (`$$...$$` / `$tag$...$tag$`), double-quoted
10
+ * identifiers, and `--`/`/* *\/` comments are left untouched, so a literal
11
+ * `$1` typed by the user in a string never gets rewritten.
12
+ *
13
+ * @param statement The SQL text `drizzle-orm/pg-proxy` generated, with `$n` placeholders.
14
+ * @param params Positional parameter values, 1-indexed to match the placeholders.
15
+ * @returns The statement with every placeholder replaced by its literal.
16
+ * @throws If the statement references `$n` beyond `params.length`.
17
+ */
18
+ export default function inlineParams(statement: string, params: unknown[]): string;
@@ -0,0 +1,118 @@
1
+ import encodeParam from './encode_param';
2
+ /**
3
+ * Substitutes `$1`, `$2`, … placeholders in a generated statement with
4
+ * literal-encoded values, so a single-statement RPC call
5
+ * (`cfni_exec(statement, params default '[]')`) never needs to bind params
6
+ * itself — `EXECUTE ... USING` cannot bind more than one differently-typed
7
+ * value the way a real driver does.
8
+ *
9
+ * Placeholders inside single-quoted strings, `E'...'` escape strings,
10
+ * dollar-quoted bodies (`$$...$$` / `$tag$...$tag$`), double-quoted
11
+ * identifiers, and `--`/`/* *\/` comments are left untouched, so a literal
12
+ * `$1` typed by the user in a string never gets rewritten.
13
+ *
14
+ * @param statement The SQL text `drizzle-orm/pg-proxy` generated, with `$n` placeholders.
15
+ * @param params Positional parameter values, 1-indexed to match the placeholders.
16
+ * @returns The statement with every placeholder replaced by its literal.
17
+ * @throws If the statement references `$n` beyond `params.length`.
18
+ */
19
+ export default function inlineParams(statement, params) {
20
+ let result = '';
21
+ let i = 0;
22
+ const len = statement.length;
23
+ while (i < len) {
24
+ const ch = statement[i];
25
+ if (ch === '-' && statement[i + 1] === '-') {
26
+ const end = statement.indexOf('\n', i);
27
+ const stop = end === -1 ? len : end + 1;
28
+ result += statement.slice(i, stop);
29
+ i = stop;
30
+ continue;
31
+ }
32
+ if (ch === '/' && statement[i + 1] === '*') {
33
+ const end = statement.indexOf('*/', i + 2);
34
+ const stop = end === -1 ? len : end + 2;
35
+ result += statement.slice(i, stop);
36
+ i = stop;
37
+ continue;
38
+ }
39
+ if (ch === "'" || (ch === 'E' && statement[i + 1] === "'")) {
40
+ const start = ch === 'E' ? i + 1 : i;
41
+ const end = findStringEnd(statement, start + 1);
42
+ result += statement.slice(i, end);
43
+ i = end;
44
+ continue;
45
+ }
46
+ if (ch === '"') {
47
+ const end = findQuotedIdentifierEnd(statement, i + 1);
48
+ result += statement.slice(i, end);
49
+ i = end;
50
+ continue;
51
+ }
52
+ if (ch === '$' && /[0-9]/.test(statement[i + 1] ?? '')) {
53
+ let j = i + 1;
54
+ while (j < len && /[0-9]/.test(statement[j]))
55
+ j++;
56
+ const index = Number(statement.slice(i + 1, j));
57
+ if (index < 1 || index > params.length) {
58
+ throw new Error(`db: statement references $${index} but only ${params.length} param(s) were provided.`);
59
+ }
60
+ result += encodeParam(params[index - 1]);
61
+ i = j;
62
+ continue;
63
+ }
64
+ if (ch === '$') {
65
+ const tagEnd = findDollarQuoteEnd(statement, i);
66
+ result += statement.slice(i, tagEnd);
67
+ i = tagEnd;
68
+ continue;
69
+ }
70
+ result += ch;
71
+ i++;
72
+ }
73
+ return result;
74
+ }
75
+ function findStringEnd(statement, from) {
76
+ let i = from;
77
+ while (i < statement.length) {
78
+ if (statement[i] === "'") {
79
+ if (statement[i + 1] === "'") {
80
+ i += 2;
81
+ continue;
82
+ }
83
+ return i + 1;
84
+ }
85
+ if (statement[i] === '\\' && statement[i - 1] !== '\\') {
86
+ // Backslash escapes are only meaningful in E'' strings, but
87
+ // skipping the next char either way is harmless here since we
88
+ // are only looking for the terminating quote.
89
+ i += 2;
90
+ continue;
91
+ }
92
+ i++;
93
+ }
94
+ return statement.length;
95
+ }
96
+ function findQuotedIdentifierEnd(statement, from) {
97
+ let i = from;
98
+ while (i < statement.length) {
99
+ if (statement[i] === '"') {
100
+ if (statement[i + 1] === '"') {
101
+ i += 2;
102
+ continue;
103
+ }
104
+ return i + 1;
105
+ }
106
+ i++;
107
+ }
108
+ return statement.length;
109
+ }
110
+ function findDollarQuoteEnd(statement, from) {
111
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(statement.slice(from));
112
+ if (!tagMatch)
113
+ return from + 1;
114
+ const tag = tagMatch[0];
115
+ const bodyStart = from + tag.length;
116
+ const closeIndex = statement.indexOf(tag, bodyStart);
117
+ return closeIndex === -1 ? statement.length : closeIndex + tag.length;
118
+ }
@@ -0,0 +1,24 @@
1
+ export interface InstallExecFile {
2
+ /** Absolute path to the file this package ships (e.g. `supabase/cfni_exec.sql`). */
3
+ sourcePath: string;
4
+ /** Absolute path to copy it to in the consuming project. */
5
+ targetPath: string;
6
+ }
7
+ export interface InstallExecOutcome {
8
+ targetPath: string;
9
+ action: 'created' | 'updated' | 'unchanged' | 'skipped-differs' | 'skipped-missing-source';
10
+ message: string;
11
+ }
12
+ /**
13
+ * Copies one packaged SQL file (`cfni_exec.sql` itself, or its pgTAP test
14
+ * file) to a target path in the consuming project, creating the target
15
+ * directory if needed.
16
+ *
17
+ * When the target already exists with different content, this skips the
18
+ * write and reports `'skipped-differs'` rather than overwriting a possibly
19
+ * customized file — pass `force: true` to overwrite anyway.
20
+ *
21
+ * @param file Source (this package's copy) and target (the project's copy) paths.
22
+ * @param force Overwrite an existing, differing target instead of skipping it.
23
+ */
24
+ export declare function installExecFile(file: InstallExecFile, force: boolean): InstallExecOutcome;
@@ -0,0 +1,42 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ /**
4
+ * Copies one packaged SQL file (`cfni_exec.sql` itself, or its pgTAP test
5
+ * file) to a target path in the consuming project, creating the target
6
+ * directory if needed.
7
+ *
8
+ * When the target already exists with different content, this skips the
9
+ * write and reports `'skipped-differs'` rather than overwriting a possibly
10
+ * customized file — pass `force: true` to overwrite anyway.
11
+ *
12
+ * @param file Source (this package's copy) and target (the project's copy) paths.
13
+ * @param force Overwrite an existing, differing target instead of skipping it.
14
+ */
15
+ export function installExecFile(file, force) {
16
+ if (!existsSync(file.sourcePath)) {
17
+ return {
18
+ targetPath: file.targetPath,
19
+ action: 'skipped-missing-source',
20
+ message: `source file not found at ${file.sourcePath}`,
21
+ };
22
+ }
23
+ const sourceContent = readFileSync(file.sourcePath, 'utf-8');
24
+ if (existsSync(file.targetPath)) {
25
+ const targetContent = readFileSync(file.targetPath, 'utf-8');
26
+ if (targetContent === sourceContent) {
27
+ return { targetPath: file.targetPath, action: 'unchanged', message: 'already up to date' };
28
+ }
29
+ if (!force) {
30
+ return {
31
+ targetPath: file.targetPath,
32
+ action: 'skipped-differs',
33
+ message: `${file.targetPath} already exists with different content — pass --force to overwrite`,
34
+ };
35
+ }
36
+ writeFileSync(file.targetPath, sourceContent);
37
+ return { targetPath: file.targetPath, action: 'updated', message: 'overwritten with --force' };
38
+ }
39
+ mkdirSync(dirname(file.targetPath), { recursive: true });
40
+ writeFileSync(file.targetPath, sourceContent);
41
+ return { targetPath: file.targetPath, action: 'created', message: 'created' };
42
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Parses one row of Postgres' composite-literal text format —
3
+ * `(field1,field2,...)`, comma-separated, each field either bare or
4
+ * `"…"`-quoted with doubled `""` for an embedded quote, and an empty bare
5
+ * field meaning `NULL` — into a positional array of field strings (or
6
+ * `null`).
7
+ *
8
+ * `cfni_exec.sql` casts every returned row with `r::text` to get this
9
+ * format, because it stays correct even with duplicate column names (a
10
+ * common shape after `select a.*, b.*`), unlike `row_to_json`, which keys
11
+ * fields by name and both collapses duplicates and re-encodes nested types
12
+ * like arrays as JSON instead of pg's own text form.
13
+ *
14
+ * @param literal A single row's `(...)`-wrapped composite-literal text.
15
+ * @returns The row's fields, in column order, `null` for a bare empty field.
16
+ */
17
+ export default function parseComposite(literal: string): (string | null)[];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Parses one row of Postgres' composite-literal text format —
3
+ * `(field1,field2,...)`, comma-separated, each field either bare or
4
+ * `"…"`-quoted with doubled `""` for an embedded quote, and an empty bare
5
+ * field meaning `NULL` — into a positional array of field strings (or
6
+ * `null`).
7
+ *
8
+ * `cfni_exec.sql` casts every returned row with `r::text` to get this
9
+ * format, because it stays correct even with duplicate column names (a
10
+ * common shape after `select a.*, b.*`), unlike `row_to_json`, which keys
11
+ * fields by name and both collapses duplicates and re-encodes nested types
12
+ * like arrays as JSON instead of pg's own text form.
13
+ *
14
+ * @param literal A single row's `(...)`-wrapped composite-literal text.
15
+ * @returns The row's fields, in column order, `null` for a bare empty field.
16
+ */
17
+ export default function parseComposite(literal) {
18
+ const fields = [];
19
+ let i = 1; // skip leading '('
20
+ const len = literal.length - 1; // index of the trailing ')'
21
+ // A single field, e.g. `()`, has no fields at all — every other case
22
+ // has at least one comma-delimited field, including a final empty one
23
+ // (a trailing comma right before the closing paren).
24
+ if (len === i)
25
+ return fields;
26
+ while (true) {
27
+ if (literal[i] === '"') {
28
+ let value = '';
29
+ i++;
30
+ while (i < len) {
31
+ if (literal[i] === '"') {
32
+ if (literal[i + 1] === '"') {
33
+ value += '"';
34
+ i += 2;
35
+ continue;
36
+ }
37
+ break;
38
+ }
39
+ value += literal[i];
40
+ i++;
41
+ }
42
+ i++; // closing quote
43
+ fields.push(value);
44
+ }
45
+ else {
46
+ let value = '';
47
+ while (i < len && literal[i] !== ',') {
48
+ value += literal[i];
49
+ i++;
50
+ }
51
+ fields.push(value === '' ? null : value);
52
+ }
53
+ if (literal[i] === ',') {
54
+ i++;
55
+ continue;
56
+ }
57
+ break;
58
+ }
59
+ return fields;
60
+ }
@@ -0,0 +1,24 @@
1
+ export type RawSqlResolution = {
2
+ status: 'true' | 'false';
3
+ reason: string;
4
+ } | {
5
+ status: 'unknown';
6
+ reason: string;
7
+ };
8
+ /**
9
+ * Locates the project's intl config file (via the `@intl-config` alias
10
+ * declared in `next.config.*`) and looks for a literal `rawSql: true|false`
11
+ * inside it, without executing the file — codegen has no TypeScript loader
12
+ * and this must not run arbitrary user code.
13
+ *
14
+ * Only handles the common case: `rawSql` written as a literal boolean in
15
+ * the config source. A value computed from a variable, function call, or
16
+ * spread is not detected — callers should treat `'unknown'` as "assume
17
+ * `true`, but warn", matching `withPublicDb`/`withUserDb`'s own default.
18
+ *
19
+ * @param cwd Project root to search from (where `next.config.*` is expected).
20
+ * @returns Whether `rawSql` was found `true`/`false`, or `'unknown'` with a
21
+ * human-readable reason (no next.config found, no alias, no db.supabase
22
+ * block, or the value isn't a literal boolean).
23
+ */
24
+ export default function resolveRawSql(cwd: string): RawSqlResolution;
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
3
+ const NEXT_CONFIG_FILE_NAMES = ['next.config.ts', 'next.config.mts', 'next.config.js', 'next.config.mjs'];
4
+ const ALIAS_PATTERN = /['"]@intl-config['"]\s*:\s*(?:path\.resolve\([^,]*,\s*)?['"]([^'"]+)['"]/;
5
+ const RAW_SQL_PATTERN = /rawSql\s*:\s*(true|false)/;
6
+ /**
7
+ * Locates the project's intl config file (via the `@intl-config` alias
8
+ * declared in `next.config.*`) and looks for a literal `rawSql: true|false`
9
+ * inside it, without executing the file — codegen has no TypeScript loader
10
+ * and this must not run arbitrary user code.
11
+ *
12
+ * Only handles the common case: `rawSql` written as a literal boolean in
13
+ * the config source. A value computed from a variable, function call, or
14
+ * spread is not detected — callers should treat `'unknown'` as "assume
15
+ * `true`, but warn", matching `withPublicDb`/`withUserDb`'s own default.
16
+ *
17
+ * @param cwd Project root to search from (where `next.config.*` is expected).
18
+ * @returns Whether `rawSql` was found `true`/`false`, or `'unknown'` with a
19
+ * human-readable reason (no next.config found, no alias, no db.supabase
20
+ * block, or the value isn't a literal boolean).
21
+ */
22
+ export default function resolveRawSql(cwd) {
23
+ const nextConfigPath = NEXT_CONFIG_FILE_NAMES.map((name) => join(cwd, name)).find((path) => existsSync(path));
24
+ if (!nextConfigPath) {
25
+ return { status: 'unknown', reason: `no next.config.* found in ${cwd}` };
26
+ }
27
+ const nextConfigSource = readFileSync(nextConfigPath, 'utf-8');
28
+ const aliasMatch = ALIAS_PATTERN.exec(nextConfigSource);
29
+ if (!aliasMatch) {
30
+ return { status: 'unknown', reason: `no "@intl-config" alias found in ${nextConfigPath}` };
31
+ }
32
+ const aliasTarget = aliasMatch[1];
33
+ const configPath = resolveConfigFile(isAbsolute(aliasTarget) ? aliasTarget : resolve(dirname(nextConfigPath), aliasTarget));
34
+ if (!configPath) {
35
+ return { status: 'unknown', reason: `"@intl-config" points at ${aliasTarget}, but no matching file was found` };
36
+ }
37
+ const configSource = readFileSync(configPath, 'utf-8');
38
+ const rawSqlMatch = RAW_SQL_PATTERN.exec(configSource);
39
+ if (!rawSqlMatch) {
40
+ return { status: 'unknown', reason: `no literal "rawSql: true|false" found in ${configPath}` };
41
+ }
42
+ return {
43
+ status: rawSqlMatch[1] === 'true' ? 'true' : 'false',
44
+ reason: `found "rawSql: ${rawSqlMatch[1]}" in ${configPath}`,
45
+ };
46
+ }
47
+ const CONFIG_EXTENSIONS = ['', '.ts', '.tsx', '.mts', '.js', '.jsx', '.mjs'];
48
+ function resolveConfigFile(basePath) {
49
+ for (const extension of CONFIG_EXTENSIONS) {
50
+ const candidate = `${basePath}${extension}`;
51
+ if (existsSync(candidate))
52
+ return candidate;
53
+ }
54
+ return null;
55
+ }
@@ -0,0 +1,9 @@
1
+ import { pgTable, pgSchema, pgEnum, pgPolicy, pgView, pgMaterializedView, index, uniqueIndex, unique, primaryKey, foreignKey, check, bigint, bigserial, integer, serial, smallint, smallserial, real, doublePrecision, numeric, boolean, text, varchar, char, uuid, date, time, timestamp, interval, json, jsonb, inet, cidr, macaddr, customType } from 'drizzle-orm/pg-core';
2
+ import { sql } from 'drizzle-orm';
3
+ /**
4
+ * Named re-exports of the `drizzle-orm/pg-core` schema builders used by
5
+ * generated Drizzle models, so codegen output can import from this package
6
+ * instead of taking a direct `drizzle-orm` dependency. Explicit named exports
7
+ * (never `export *`) keep the re-export surface tree-shakeable.
8
+ */
9
+ export { pgTable, pgSchema, pgEnum, pgPolicy, pgView, pgMaterializedView, index, uniqueIndex, unique, primaryKey, foreignKey, check, bigint, bigserial, integer, serial, smallint, smallserial, real, doublePrecision, numeric, boolean, text, varchar, char, uuid, date, time, timestamp, interval, json, jsonb, inet, cidr, macaddr, customType, sql, };
@@ -0,0 +1,9 @@
1
+ import { pgTable, pgSchema, pgEnum, pgPolicy, pgView, pgMaterializedView, index, uniqueIndex, unique, primaryKey, foreignKey, check, bigint, bigserial, integer, serial, smallint, smallserial, real, doublePrecision, numeric, boolean, text, varchar, char, uuid, date, time, timestamp, interval, json, jsonb, inet, cidr, macaddr, customType, } from 'drizzle-orm/pg-core';
2
+ import { sql } from 'drizzle-orm';
3
+ /**
4
+ * Named re-exports of the `drizzle-orm/pg-core` schema builders used by
5
+ * generated Drizzle models, so codegen output can import from this package
6
+ * instead of taking a direct `drizzle-orm` dependency. Explicit named exports
7
+ * (never `export *`) keep the re-export surface tree-shakeable.
8
+ */
9
+ export { pgTable, pgSchema, pgEnum, pgPolicy, pgView, pgMaterializedView, index, uniqueIndex, unique, primaryKey, foreignKey, check, bigint, bigserial, integer, serial, smallint, smallserial, real, doublePrecision, numeric, boolean, text, varchar, char, uuid, date, time, timestamp, interval, json, jsonb, inet, cidr, macaddr, customType, sql, };
@@ -0,0 +1,178 @@
1
+ /**
2
+ * One filter's operator and value, e.g. `{ age: ['gt', 18] }`. The bare-value
3
+ * form `{ id: 5 }` (see {@link SupabaseWhere}) is shorthand for `['eq', 5]`.
4
+ * `['not', operator, value]` negates any operator (`['not', 'eq', 5]` is
5
+ * `column <> 5` via PostgREST's `not.eq.5`).
6
+ *
7
+ * This is the complete operator set `@supabase/supabase-js`'s query builder
8
+ * exposes for `.from(table)` — there's no separate "Supabase client" query
9
+ * layer on top of `postgrest-js`; `supabase.from()` returns a `postgrest-js`
10
+ * builder directly, so this list is also the ceiling for what `supabase-js`
11
+ * itself can express against a single table. What's still out of reach:
12
+ * joins/embedded resources (pass a nested `columns` string instead — that's
13
+ * plain PostgREST syntax, not a filter), and anything needing more than one
14
+ * table in a single round-trip. Those need `cfni_exec`/`withPublicDb`/
15
+ * `withUserDb`, or a Postgres function called via {@link supabaseRpc}.
16
+ */
17
+ export type SupabaseFilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'likeAllOf' | 'likeAnyOf' | 'ilike' | 'ilikeAllOf' | 'ilikeAnyOf' | 'regexMatch' | 'regexIMatch' | 'is' | 'isDistinct' | 'in' | 'contains' | 'containedBy' | 'overlaps' | 'rangeGt' | 'rangeGte' | 'rangeLt' | 'rangeLte' | 'rangeAdjacent';
18
+ export type SupabaseFilterValue = unknown | [SupabaseFilterOperator, unknown] | ['not', SupabaseFilterOperator, unknown];
19
+ /** Equality (or `[operator, value]`) filters, ANDed together column by column. */
20
+ export type SupabaseWhere = Record<string, SupabaseFilterValue>;
21
+ export interface SupabaseTextSearch {
22
+ column: string;
23
+ query: string;
24
+ type?: 'plain' | 'phrase' | 'websearch';
25
+ config?: string;
26
+ }
27
+ export interface SupabaseOrderBy {
28
+ column: string;
29
+ ascending?: boolean;
30
+ nullsFirst?: boolean;
31
+ }
32
+ export interface SupabaseSelectOptions {
33
+ /** Column list, PostgREST syntax (e.g. `'id,name'`, or `'*, related(*)'` for an embedded resource). Defaults to `'*'`. */
34
+ columns?: string;
35
+ /** Filters, ANDed together — see {@link SupabaseWhere}. */
36
+ where?: SupabaseWhere;
37
+ /** Shorthand for several `eq` filters at once — PostgREST's `.match()`. ANDed with `where`. */
38
+ match?: Record<string, unknown>;
39
+ /** A raw PostgREST `or()` filter string (e.g. `'age.gt.18,status.eq.active'`), for filters spanning multiple columns. ANDed with `where`/`match`. */
40
+ or?: string | {
41
+ filters: string;
42
+ referencedTable?: string;
43
+ };
44
+ /** Full-text search via PostgREST's `@@` operators. */
45
+ textSearch?: SupabaseTextSearch;
46
+ /** One or more `order by` clauses, applied in array order. */
47
+ orderBy?: SupabaseOrderBy | SupabaseOrderBy[];
48
+ /** Maximum rows to return. */
49
+ limit?: number;
50
+ /** Zero-indexed, inclusive `[from, to]` row range — PostgREST pagination. */
51
+ range?: [number, number];
52
+ /** Resolve to the single matching row instead of an array; errors if there isn't exactly one. */
53
+ single?: boolean;
54
+ /** Resolve to the single matching row, or `null` if there are none; errors if there's more than one. */
55
+ maybeSingle?: boolean;
56
+ /** Also return the total matching row count (`'exact'`, `'planned'`, or `'estimated'`). */
57
+ count?: 'exact' | 'planned' | 'estimated';
58
+ }
59
+ export interface SupabaseMutationOptions {
60
+ /** Filters, ANDed together — see {@link SupabaseWhere}. At least one of `where`/`match`/`or` is required for update/delete. */
61
+ where?: SupabaseWhere;
62
+ /** Shorthand for several `eq` filters at once. ANDed with `where`. */
63
+ match?: Record<string, unknown>;
64
+ /** A raw PostgREST `or()` filter string, for filters spanning multiple columns. ANDed with `where`/`match`. */
65
+ or?: string | {
66
+ filters: string;
67
+ referencedTable?: string;
68
+ };
69
+ }
70
+ export interface SupabaseUpsertOptions {
71
+ /** Column(s) forming the conflict target, comma-separated. Defaults to the table's primary key. */
72
+ onConflict?: string;
73
+ /** Skip (rather than update) rows that already exist. Defaults to `false`. */
74
+ ignoreDuplicates?: boolean;
75
+ }
76
+ export interface SupabaseResult<T> {
77
+ rows: T[];
78
+ /** Present only when `count` was requested on a select. */
79
+ count: number | null;
80
+ }
81
+ /**
82
+ * Selects rows from `table` through the Supabase REST API
83
+ * (`@supabase/supabase-js`'s `.from(table).select()`), as the **anonymous**
84
+ * role — no `cfni_exec`, no raw SQL, only what PostgREST's REST API itself
85
+ * supports: a column list, filters (equality or a named operator), one or
86
+ * more `order by` clauses, `limit`/`range` pagination, `single`/`maybeSingle`,
87
+ * and an optional row count.
88
+ *
89
+ * Reach for this — or {@link supabaseSelectAsUser} — instead of
90
+ * `withPublicDb`/`withUserDb` when `db.supabase.rawSql` is `false`, or
91
+ * whenever a query is simple enough that avoiding `cfni_exec` entirely is
92
+ * preferable. Cross-table joins/aggregates spanning more than an embedded
93
+ * resource (`columns: '*, related(*)'`) still need `withPublicDb`/
94
+ * `withUserDb`, or a Postgres function called via {@link supabaseRpc}.
95
+ *
96
+ * @param table Table (or view) name.
97
+ * @param options Column list, filters, ordering, pagination, count.
98
+ * @returns The matching rows, and the total count when `options.count` is set.
99
+ * @throws If `db`/`db.supabase` is not set, or PostgREST rejects the request.
100
+ */
101
+ export declare function supabaseSelect<T = Record<string, unknown>>(table: string, options?: SupabaseSelectOptions): Promise<SupabaseResult<T>>;
102
+ /**
103
+ * Same as {@link supabaseSelect}, but authenticated as the **signed-in
104
+ * user**: the bearer token comes from `db.getAccessToken`, or the signed-in
105
+ * Firebase user's ID token when `firebaseAuth` is configured — see
106
+ * {@link resolveAccessToken}. PostgREST resolves the caller as
107
+ * `authenticated` and RLS applies exactly as it would for `withUserDb`.
108
+ */
109
+ export declare function supabaseSelectAsUser<T = Record<string, unknown>>(table: string, options?: SupabaseSelectOptions): Promise<SupabaseResult<T>>;
110
+ /**
111
+ * Inserts one or more rows into `table` through the Supabase REST API, as
112
+ * the **anonymous** role. See {@link supabaseSelect} for when to use this
113
+ * over `withPublicDb`.
114
+ *
115
+ * @param table Table name.
116
+ * @param values A single row, or an array of rows, to insert.
117
+ * @returns The inserted row(s) as returned by PostgREST.
118
+ */
119
+ export declare function supabaseInsert<T = Record<string, unknown>>(table: string, values: Record<string, unknown> | Record<string, unknown>[]): Promise<T[]>;
120
+ /** Same as {@link supabaseInsert}, authenticated as the signed-in user — see {@link supabaseSelectAsUser}. */
121
+ export declare function supabaseInsertAsUser<T = Record<string, unknown>>(table: string, values: Record<string, unknown> | Record<string, unknown>[]): Promise<T[]>;
122
+ /**
123
+ * Inserts one or more rows into `table`, or updates them on a conflict —
124
+ * PostgREST's `upsert`, i.e. `insert ... on conflict (...) do update`. As
125
+ * the **anonymous** role.
126
+ *
127
+ * @param table Table name.
128
+ * @param values A single row, or an array of rows, to upsert.
129
+ * @param options `onConflict` (defaults to the table's primary key) and
130
+ * `ignoreDuplicates` (skip instead of update on conflict).
131
+ * @returns The upserted row(s) as returned by PostgREST.
132
+ */
133
+ export declare function supabaseUpsert<T = Record<string, unknown>>(table: string, values: Record<string, unknown> | Record<string, unknown>[], options?: SupabaseUpsertOptions): Promise<T[]>;
134
+ /** Same as {@link supabaseUpsert}, authenticated as the signed-in user — see {@link supabaseSelectAsUser}. */
135
+ export declare function supabaseUpsertAsUser<T = Record<string, unknown>>(table: string, values: Record<string, unknown> | Record<string, unknown>[], options?: SupabaseUpsertOptions): Promise<T[]>;
136
+ /**
137
+ * Updates rows in `table` matching `options.where` through the Supabase
138
+ * REST API, as the **anonymous** role. See {@link supabaseSelect} for when
139
+ * to use this over `withPublicDb`.
140
+ *
141
+ * @param table Table name.
142
+ * @param values Columns to set.
143
+ * @param options `where` — filters selecting which rows to update; required,
144
+ * since an unfiltered update would touch every row.
145
+ * @returns The updated rows as returned by PostgREST.
146
+ */
147
+ export declare function supabaseUpdate<T = Record<string, unknown>>(table: string, values: Record<string, unknown>, options: SupabaseMutationOptions): Promise<T[]>;
148
+ /** Same as {@link supabaseUpdate}, authenticated as the signed-in user — see {@link supabaseSelectAsUser}. */
149
+ export declare function supabaseUpdateAsUser<T = Record<string, unknown>>(table: string, values: Record<string, unknown>, options: SupabaseMutationOptions): Promise<T[]>;
150
+ /**
151
+ * Deletes rows in `table` matching `options.where` through the Supabase REST
152
+ * API, as the **anonymous** role. See {@link supabaseSelect} for when to
153
+ * use this over `withPublicDb`.
154
+ *
155
+ * @param table Table name.
156
+ * @param options `where` — filters selecting which rows to delete; required,
157
+ * since an unfiltered delete would remove every row.
158
+ * @returns The deleted rows as returned by PostgREST.
159
+ */
160
+ export declare function supabaseDelete<T = Record<string, unknown>>(table: string, options: SupabaseMutationOptions): Promise<T[]>;
161
+ /** Same as {@link supabaseDelete}, authenticated as the signed-in user — see {@link supabaseSelectAsUser}. */
162
+ export declare function supabaseDeleteAsUser<T = Record<string, unknown>>(table: string, options: SupabaseMutationOptions): Promise<T[]>;
163
+ /**
164
+ * Calls a Postgres function through PostgREST's `.rpc()` — the same
165
+ * mechanism `cfni_exec` itself uses, but for a function you define yourself.
166
+ * This is how to run anything the `select`/`insert`/`upsert`/`update`/
167
+ * `delete` helpers above can't express (joins, aggregates, custom logic)
168
+ * without needing `cfni_exec`/raw SQL: write a regular Postgres function,
169
+ * `grant execute` to `anon`/`authenticated`, and call it by name.
170
+ *
171
+ * As the **anonymous** role.
172
+ *
173
+ * @param fn Function name.
174
+ * @param args Named arguments, matching the function's parameter names.
175
+ */
176
+ export declare function supabaseRpc<T = unknown>(fn: string, args?: Record<string, unknown>): Promise<T>;
177
+ /** Same as {@link supabaseRpc}, authenticated as the signed-in user — see {@link supabaseSelectAsUser}. */
178
+ export declare function supabaseRpcAsUser<T = unknown>(fn: string, args?: Record<string, unknown>): Promise<T>;