@carbonorm/carbonnode 6.0.6 → 6.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +159 -28
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +156 -28
- package/dist/index.esm.js.map +1 -1
- package/dist/utils/colorSql.d.ts +1 -0
- package/dist/variables/getEnv.d.ts +10 -0
- package/dist/variables/isTest.d.ts +1 -1
- package/package.json +2 -1
- package/src/__tests__/sakila-db/C6.js +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.json +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.sql +1 -1
- package/src/__tests__/sakila-db/C6.ts +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
- package/src/__tests__/sqlBuilders.test.ts +21 -1
- package/src/api/restRequest.ts +9 -10
- package/src/executors/HttpExecutor.ts +1 -1
- package/src/index.ts +3 -1
- package/src/orm/queries/SelectQueryBuilder.ts +14 -1
- package/src/utils/colorSql.ts +112 -0
- package/src/variables/getEnv.ts +71 -0
- package/src/variables/isLocal.ts +2 -2
- package/src/variables/isTest.ts +4 -4
- package/src/variables/isVerbose.ts +2 -3
- package/dist/variables/getEnvVar.d.ts +0 -1
- package/src/variables/getEnvVar.ts +0 -15
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { C6C } from '../constants/C6Constants';
|
|
3
3
|
import { SelectQueryBuilder } from '../orm/queries/SelectQueryBuilder';
|
|
4
4
|
import { PostQueryBuilder } from '../orm/queries/PostQueryBuilder';
|
|
5
5
|
import { UpdateQueryBuilder } from '../orm/queries/UpdateQueryBuilder';
|
|
6
6
|
import { DeleteQueryBuilder } from '../orm/queries/DeleteQueryBuilder';
|
|
7
7
|
import { buildTestConfig, buildBinaryTestConfig, buildBinaryTestConfigFqn } from './fixtures/c6.fixture';
|
|
8
|
+
import { version } from '../../package.json';
|
|
8
9
|
|
|
9
10
|
describe('SQL Builders', () => {
|
|
10
11
|
it('builds SELECT with JOIN, WHERE, GROUP BY, HAVING and default LIMIT', () => {
|
|
@@ -43,6 +44,25 @@ describe('SQL Builders', () => {
|
|
|
43
44
|
expect(params).toEqual(['%A%', 10, 1]);
|
|
44
45
|
});
|
|
45
46
|
|
|
47
|
+
it('logs SELECT statements with package version', () => {
|
|
48
|
+
const config = buildTestConfig();
|
|
49
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
50
|
+
const qb = new SelectQueryBuilder(config as any, {
|
|
51
|
+
SELECT: ['actor.first_name'],
|
|
52
|
+
} as any, false);
|
|
53
|
+
|
|
54
|
+
qb.build('actor');
|
|
55
|
+
|
|
56
|
+
const logLines = logSpy.mock.calls
|
|
57
|
+
.map((call) => call[0])
|
|
58
|
+
.filter((entry): entry is string => typeof entry === 'string');
|
|
59
|
+
const selectLine = logLines.find((line) => line.includes('[SELECT]'));
|
|
60
|
+
|
|
61
|
+
expect(selectLine).toBeDefined();
|
|
62
|
+
expect(selectLine).toContain(`[${version}]`);
|
|
63
|
+
logSpy.mockRestore();
|
|
64
|
+
});
|
|
65
|
+
|
|
46
66
|
it('builds INSERT with ON DUPLICATE KEY UPDATE', () => {
|
|
47
67
|
const config = buildTestConfig();
|
|
48
68
|
const qb = new PostQueryBuilder(config as any, {
|
package/src/api/restRequest.ts
CHANGED
|
@@ -35,22 +35,21 @@ export default function restRequest<
|
|
|
35
35
|
|
|
36
36
|
config.verbose ??= isVerbose(); // Default to env-driven verbosity if not set
|
|
37
37
|
|
|
38
|
+
if (!config.mysqlPool && !config.axios) {
|
|
39
|
+
throw new Error("No execution method available: neither mysqlPool nor axios instance provided in config.");
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
// SQL path if on Node with a provided pool
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
console.log("Using SQL Executor");
|
|
42
|
-
}
|
|
43
|
+
if (config.mysqlPool) {
|
|
44
|
+
config.verbose && console.log("Using SQL Executor");
|
|
43
45
|
const {SqlExecutor} = await import('../executors/SqlExecutor');
|
|
44
46
|
const executor = new SqlExecutor<G>(config, request);
|
|
45
47
|
return executor.execute();
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
hasPool: !!config.mysqlPool
|
|
52
|
-
});
|
|
53
|
-
}
|
|
50
|
+
config.verbose && console.log("Using HTTP Executor", {
|
|
51
|
+
isNode: isNode(),
|
|
52
|
+
});
|
|
54
53
|
|
|
55
54
|
// HTTP path fallback
|
|
56
55
|
const {HttpExecutor} = await import('../executors/HttpExecutor');
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,8 @@ export * from "./types/ormGenerics";
|
|
|
33
33
|
export * from "./types/ormInterfaces";
|
|
34
34
|
export * from "./utils/apiHelpers";
|
|
35
35
|
export * from "./utils/cacheManager";
|
|
36
|
+
export { default as colorSql } from "./utils/colorSql";
|
|
37
|
+
export * from "./utils/colorSql";
|
|
36
38
|
export * from "./utils/determineRuntimeJsType";
|
|
37
39
|
export * from "./utils/logger";
|
|
38
40
|
export * from "./utils/normalizeSingularRequest";
|
|
@@ -41,7 +43,7 @@ export * from "./utils/sqlAllowList";
|
|
|
41
43
|
export * from "./utils/testHelpers";
|
|
42
44
|
export * from "./utils/toastNotifier";
|
|
43
45
|
export * from "./utils/toastRuntime";
|
|
44
|
-
export * from "./variables/
|
|
46
|
+
export * from "./variables/getEnv";
|
|
45
47
|
export { default as isLocal } from "./variables/isLocal";
|
|
46
48
|
export * from "./variables/isLocal";
|
|
47
49
|
export { default as isNode } from "./variables/isNode";
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import {OrmGenerics} from "../../types/ormGenerics";
|
|
2
2
|
import {PaginationBuilder} from "../builders/PaginationBuilder";
|
|
3
3
|
import {SqlBuilderResult} from "../utils/sqlUtils";
|
|
4
|
+
import {getEnvBool} from "../../variables/getEnv";
|
|
5
|
+
import colorSql from "../../utils/colorSql";
|
|
6
|
+
import { version } from "../../../package.json";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const C = {
|
|
10
|
+
SSR: "\x1b[95m", // bright magenta
|
|
11
|
+
HTTP: "\x1b[94m", // bright blue
|
|
12
|
+
SQL: "\x1b[96m", // bright cyan (your SELECT teal)
|
|
13
|
+
RESET: "\x1b[0m",
|
|
14
|
+
};
|
|
4
15
|
|
|
5
16
|
export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder<G>{
|
|
6
17
|
|
|
@@ -56,7 +67,9 @@ export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder
|
|
|
56
67
|
sql += ` LIMIT 100`;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
const preText = getEnvBool("SSR", false) ? `${C.SSR}[SSR]${C.RESET} ` : `${C.HTTP}[API]${C.RESET} `;
|
|
71
|
+
|
|
72
|
+
console.log(`[${version}] ${preText}${C.SQL}[SELECT]${C.RESET} ${colorSql(sql)}`);
|
|
60
73
|
|
|
61
74
|
return { sql, params };
|
|
62
75
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* eslint-disable no-control-regex */
|
|
2
|
+
|
|
3
|
+
const RESET = "\x1b[0m";
|
|
4
|
+
|
|
5
|
+
const C = {
|
|
6
|
+
KEYWORD: "\x1b[94m", // blue
|
|
7
|
+
LIMIT: "\x1b[93m", // yellow
|
|
8
|
+
NUMBER: "\x1b[92m", // green
|
|
9
|
+
DIM: "\x1b[90m", // gray
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/* ---------- ANSI helpers ---------- */
|
|
13
|
+
|
|
14
|
+
const ansi256 = (n: number) => `\x1b[38;5;${n}m`;
|
|
15
|
+
|
|
16
|
+
/* ---------- hashing ---------- */
|
|
17
|
+
|
|
18
|
+
function hashString(str: string): number {
|
|
19
|
+
let hash = 0;
|
|
20
|
+
for (let i = 0; i < str.length; i++) {
|
|
21
|
+
hash = (hash * 31 + str.charCodeAt(i)) | 0;
|
|
22
|
+
}
|
|
23
|
+
return Math.abs(hash);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ---------- table color ---------- */
|
|
27
|
+
|
|
28
|
+
function tableRGB(tableName: string): [number, number, number] {
|
|
29
|
+
const name = tableName.replace(/[`"]/g, "").toLowerCase();
|
|
30
|
+
const hash = hashString(name);
|
|
31
|
+
|
|
32
|
+
// Stable hue bucket by first letter
|
|
33
|
+
const first = name.charCodeAt(0) || 97;
|
|
34
|
+
const hueBase = (first - 97) % 6;
|
|
35
|
+
|
|
36
|
+
const r = (hueBase + (hash % 3)) % 6;
|
|
37
|
+
const g = (hash >> 3) % 6;
|
|
38
|
+
const b = (hash >> 6) % 6;
|
|
39
|
+
|
|
40
|
+
return [r, g, Math.max(2, b)]; // avoid muddy dark blues
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function tableColor(table: string): string {
|
|
44
|
+
const [r, g, b] = tableRGB(table);
|
|
45
|
+
return ansi256(16 + 36 * r + 6 * g + b);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ---------- column color (same hue, lighter) ---------- */
|
|
49
|
+
|
|
50
|
+
function columnColorFromTable(table: string): string {
|
|
51
|
+
const [r, g, b] = tableRGB(table);
|
|
52
|
+
|
|
53
|
+
// Lift toward white, preserve hue
|
|
54
|
+
const lr = Math.min(5, r + 1);
|
|
55
|
+
const lg = Math.min(5, g + 1);
|
|
56
|
+
const lb = Math.min(5, b + 2);
|
|
57
|
+
|
|
58
|
+
return ansi256(16 + 36 * lr + 6 * lg + lb);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ---------- bind collapsing ---------- */
|
|
62
|
+
/**
|
|
63
|
+
* ?, ?, ?, ?, ?, ? → ? ×6
|
|
64
|
+
* triggers at 4+
|
|
65
|
+
*/
|
|
66
|
+
function collapseBinds(sql: string): string {
|
|
67
|
+
return sql.replace(
|
|
68
|
+
/(\?\s*,\s*){3,}\?/g,
|
|
69
|
+
(match) => {
|
|
70
|
+
const count = match.split("?").length - 1;
|
|
71
|
+
return `${C.DIM}? ×${count}${RESET}`;
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ---------- main formatter ---------- */
|
|
77
|
+
|
|
78
|
+
export default function colorSql(sql: string): string {
|
|
79
|
+
let s = sql.trim();
|
|
80
|
+
|
|
81
|
+
/* 1️⃣ collapse bind noise */
|
|
82
|
+
s = collapseBinds(s);
|
|
83
|
+
|
|
84
|
+
/* 2️⃣ table.column coloring (core visual grouping) */
|
|
85
|
+
s = s.replace(
|
|
86
|
+
/\b(`?\w+`?)\.(\w+)\b/g,
|
|
87
|
+
(_, table, column) =>
|
|
88
|
+
`${tableColor(table)}${table}${RESET}.` +
|
|
89
|
+
`${columnColorFromTable(table)}${column}${RESET}`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/* 3️⃣ FROM / JOIN tables */
|
|
93
|
+
s = s.replace(
|
|
94
|
+
/\b(FROM|JOIN|UPDATE|INTO)\s+(`[^`]+`|\w+)/gi,
|
|
95
|
+
(_, kw, table) =>
|
|
96
|
+
`${C.KEYWORD}${kw}${RESET} ${tableColor(table)}${table}${RESET}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
/* 4️⃣ SQL keywords */
|
|
100
|
+
s = s.replace(
|
|
101
|
+
/\b(SELECT|WHERE|AND|OR|ON|IN|BETWEEN|EXISTS|ORDER BY)\b/gi,
|
|
102
|
+
`${C.KEYWORD}$1${RESET}`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/* 5️⃣ LIMIT */
|
|
106
|
+
s = s.replace(
|
|
107
|
+
/\bLIMIT\s+(\d+)/gi,
|
|
108
|
+
`${C.LIMIT}LIMIT${RESET} ${C.NUMBER}$1${RESET}`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return s;
|
|
112
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: working with global browser objects */
|
|
2
|
+
function getRuntimeEnv(key: string): any {
|
|
3
|
+
return typeof window !== "undefined" && (window as any).__ENV__?.[key];
|
|
4
|
+
}
|
|
5
|
+
// Do not import anything here
|
|
6
|
+
function getViteEnv(key: string): any {
|
|
7
|
+
// @ts-expect-error
|
|
8
|
+
return typeof import.meta !== "undefined" && import.meta.env?.[key];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getEnv(key: string, fallback?: string): string;
|
|
12
|
+
export function getEnv<T>(key: string, fallback: T): T;
|
|
13
|
+
export function getEnv<T = string>(key: string, fallback?: T): T {
|
|
14
|
+
try {
|
|
15
|
+
const viteEnv = getViteEnv(key);
|
|
16
|
+
if (viteEnv !== undefined) return viteEnv as T;
|
|
17
|
+
} catch {}
|
|
18
|
+
|
|
19
|
+
const runtimeEnv = getRuntimeEnv(key);
|
|
20
|
+
if (runtimeEnv !== undefined) return runtimeEnv as T;
|
|
21
|
+
|
|
22
|
+
if (typeof process !== "undefined" && process.env?.[key] !== undefined) {
|
|
23
|
+
return process.env[key] as T;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (fallback !== undefined) return fallback;
|
|
27
|
+
throw new Error(`Missing environment variable: ${key}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getEnvBool(key: string, fallback = false): boolean {
|
|
31
|
+
const raw = getEnv(key, fallback);
|
|
32
|
+
const v = String(raw).trim().toLowerCase();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
v === "true" ||
|
|
36
|
+
v === "1" ||
|
|
37
|
+
v === "yes" ||
|
|
38
|
+
v === "y" ||
|
|
39
|
+
v === "on" ||
|
|
40
|
+
v === "enabled"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type EnvSource = "vite" | "runtime" | "process" | "fallback" | "missing";
|
|
45
|
+
|
|
46
|
+
export function getEnvDebug<T = string>(
|
|
47
|
+
key: string,
|
|
48
|
+
fallback?: T,
|
|
49
|
+
): { key: string; value: T; source: EnvSource } {
|
|
50
|
+
try {
|
|
51
|
+
const viteEnv = getViteEnv(key);
|
|
52
|
+
if (viteEnv !== undefined) {
|
|
53
|
+
return { key, value: viteEnv as T, source: "vite" };
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
|
|
57
|
+
const runtimeEnv = getRuntimeEnv(key);
|
|
58
|
+
if (runtimeEnv !== undefined) {
|
|
59
|
+
return { key, value: runtimeEnv as T, source: "runtime" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof process !== "undefined" && process.env?.[key] !== undefined) {
|
|
63
|
+
return { key, value: process.env[key] as T, source: "process" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (fallback !== undefined) {
|
|
67
|
+
return { key, value: fallback, source: "fallback" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { key, value: undefined as any, source: "missing" };
|
|
71
|
+
}
|
package/src/variables/isLocal.ts
CHANGED
package/src/variables/isTest.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getEnv, getEnvBool} from "./getEnv";
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export default function () {
|
|
5
|
-
return
|
|
6
|
-
||
|
|
7
|
-
||
|
|
5
|
+
return getEnv('JEST_WORKER_ID', null) || getEnv('NODE_ENV', "") === 'test'
|
|
6
|
+
|| getEnvBool('REACT_APP_TEST', false) || getEnvBool('VITE_TEST', false)
|
|
7
|
+
|| getEnv('MODE', '') === 'test' || getEnvBool('VITE_TEST_MODE', false);
|
|
8
8
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getEnvBool} from "./getEnv";
|
|
2
2
|
|
|
3
3
|
export default function () {
|
|
4
|
-
|
|
5
|
-
return ['true', '1', 'yes', 'on'].includes(envVerbose.toLowerCase());
|
|
4
|
+
return getEnvBool('VERBOSE', false) || getEnvBool('REACT_APP_VERBOSE', false) || getEnvBool('VITE_VERBOSE', false)
|
|
6
5
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function getEnvVar(key: string, fallback?: string): string;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function getEnvVar(key: string, fallback = ''): string {
|
|
2
|
-
// Vite-style injection
|
|
3
|
-
// @ts-ignore
|
|
4
|
-
if (typeof import.meta !== 'undefined' && import.meta.env && key in import.meta.env) {
|
|
5
|
-
// @ts-ignore
|
|
6
|
-
return import.meta.env[key];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Node or SSR
|
|
10
|
-
if (typeof process !== 'undefined' && process.env && key in process.env) {
|
|
11
|
-
return process.env[key]!;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return fallback;
|
|
15
|
-
}
|