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.
- package/README.md +217 -12
- package/bin/db_codegen.mjs +58 -8
- package/bin/db_install_exec.mjs +16 -0
- package/bin/install_exec_step.mjs +39 -0
- package/dist/src/db/codegen_paths.d.ts +12 -0
- package/dist/src/db/codegen_paths.js +33 -2
- package/dist/src/db/context.js +19 -1
- package/dist/src/db/encode_param.d.ts +10 -0
- package/dist/src/db/encode_param.js +60 -0
- package/dist/src/db/helpers.d.ts +2 -2
- package/dist/src/db/helpers.js +2 -2
- package/dist/src/db/index.d.ts +9 -0
- package/dist/src/db/index.js +8 -0
- package/dist/src/db/inline_params.d.ts +18 -0
- package/dist/src/db/inline_params.js +118 -0
- package/dist/src/db/install_exec.d.ts +24 -0
- package/dist/src/db/install_exec.js +42 -0
- package/dist/src/db/parse_composite.d.ts +17 -0
- package/dist/src/db/parse_composite.js +60 -0
- package/dist/src/db/resolve_raw_sql.d.ts +24 -0
- package/dist/src/db/resolve_raw_sql.js +55 -0
- package/dist/src/db/schema.d.ts +9 -0
- package/dist/src/db/schema.js +9 -0
- package/dist/src/db/supabase_rest.d.ts +178 -0
- package/dist/src/db/supabase_rest.js +262 -0
- package/dist/src/db/supabase_transport.d.ts +8 -0
- package/dist/src/db/supabase_transport.js +41 -2
- package/dist/src/types/types.d.ts +13 -0
- package/llms.txt +4 -2
- package/package.json +278 -273
- package/supabase/cfni_exec.sql +131 -16
- package/supabase/tests/cfni_exec.sql +150 -0
package/dist/src/db/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/src/db/index.js
CHANGED
|
@@ -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>;
|