Skip to content

Commit 98a6bce

Browse files
committed
Removing Temporal and adding Hub
1 parent 158eb01 commit 98a6bce

File tree

9 files changed

+367
-184
lines changed

9 files changed

+367
-184
lines changed

deno.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@
1212
},
1313
"imports": {
1414
"@std/assert": "jsr:@std/assert@^1",
15+
"@std/async": "jsr:@std/async@^1",
16+
"@std/cache": "jsr:@std/cache@^0",
1517
"@std/collections": "jsr:@std/collections@^1",
16-
"@std/fmt": "jsr:@std/fmt@^1",
17-
"@std/log": "jsr:@std/log@^0"
18+
"@std/fmt": "jsr:@std/fmt@^1"
1819
},
1920
"lock": false,
2021
"tasks": {
2122
"check": "deno check **/*.ts && deno lint && deno fmt --check",
2223
"lint": "deno lint src test",
2324
"release": "release",
24-
"test": "deno test -A --unstable-kv --unstable-temporal",
25+
"test": "deno test -A --unstable-kv",
2526
"test-mysql": "./test/test-mysql.sh",
2627
"test-postgres": "./test/test-postgres.sh"
2728
}

src/db.ts

Lines changed: 19 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { blue, bold, white } from "@std/fmt/colors";
2-
import { ConsoleHandler, getLogger, type LevelName, type Logger } from "@std/log";
31
import { DDL } from "./ddl.ts";
4-
import type { Class, Identifiable, Parameter, Row, Schema, Temporal } from "./types.ts";
2+
import type { Class, Identifiable, Parameter, Row, Schema } from "./types.ts";
53
import { Repository } from "./repository.ts";
64

75
// Import Driver Types
@@ -12,9 +10,8 @@ import type { Client as Postgres } from "jsr:@dewars/postgres@0";
1210
type Values<T> = T[keyof T];
1311

1412
// Syntactic Sugar
15-
function clean(sql: string, color = true): string {
16-
sql = DB._sqlFilter(sql).replaceAll(/[ \n\r\t]+/g, " ").trim();
17-
return color ? white(sql) : sql;
13+
function clean(sql: string): string {
14+
return DB._sqlFilter(sql).replaceAll(/[ \n\r\t]+/g, " ").trim();
1815
}
1916

2017
const Hook = {
@@ -64,12 +61,9 @@ async function connect(config: ClientConfig): Promise<Client> {
6461
password: Deno.env.get("DB_PASS"),
6562
}, config);
6663

67-
// Set the debug flag
68-
DB.debug = Deno.env.get("DEBUG")?.includes("dbx") || config.debug || false;
69-
7064
// Cleans parameters of temporal values
71-
const isTemporal = (p: unknown): p is Temporal => p instanceof Temporal.PlainDate || p instanceof Temporal.PlainDateTime || p instanceof Temporal.PlainTime;
72-
const cleanTemporals = (parameters: Parameter[] | undefined) => parameters?.map((p) => isTemporal(p) ? p.toString() : p);
65+
const isTemporal = (p: unknown) => p && ["PlainDate", "PlainDateTime", "PlainTime"].includes(p?.constructor?.name);
66+
const cleanTemporals = (parameters: Parameter[] | undefined) => parameters?.map((p) => isTemporal(p) ? p!.toString() : p);
7367

7468
// MySQL
7569
if (config.type === Provider.MYSQL) {
@@ -147,27 +141,22 @@ async function connect(config: ClientConfig): Promise<Client> {
147141
}
148142

149143
export class DB {
144+
static readonly LEVELS = ["debug", "info", "warn", "error", "none"];
150145
static Hook = Hook;
151146
static Provider = Provider;
152147
static readonly ALL = Number.MAX_SAFE_INTEGER;
153148
static client: Client;
154-
static debug = false;
155149
static #schemas = new Map<string, Schema>();
150+
static logger = DB.#createLogger();
156151
static type: string;
157152

158-
static get logger(): Logger {
159-
return this.mainLogger();
160-
}
161-
162-
// Get parent logger and if the logger has not been set, it will add a handler and level
163-
static mainLogger(level: LevelName = "INFO"): Logger {
164-
const logger = getLogger("dbx");
165-
if (logger.levelName !== "NOTSET") return logger;
166-
167-
// Attach console handler if not set, set debug flag, and initial level
168-
if (logger.handlers.length === 0) logger.handlers.push(new ConsoleHandler("DEBUG"));
169-
const debug = Deno.env.get("DEBUG")?.includes(logger.loggerName) || this.debug;
170-
logger.levelName = debug ? "DEBUG" : level;
153+
static #createLogger(level: typeof DB.LEVELS[number] = "info"): Console & { level: string } {
154+
const logger = Object.setPrototypeOf({ n: DB.LEVELS.indexOf(level) }, console) as Console & { level: string, n: number };
155+
DB.LEVELS.forEach((l, i) => (logger as any)[l] = (...args: unknown[]) => logger.n <= i ? (console as any)[l](...args) : () => {});
156+
Object.defineProperty(logger, "level", {
157+
get: function () { return DB.LEVELS[this.n]; },
158+
set: function (l: typeof DB.LEVELS[number]) { return this.n = DB.LEVELS.indexOf(l) },
159+
});
171160
return logger;
172161
}
173162

@@ -187,7 +176,9 @@ export class DB {
187176
DB.client = await connect(config);
188177

189178
// Should wrap in debugger?
190-
if (this.debug) DB.client = new DebugClient(DB.client);
179+
// Deprecating behavior.
180+
// const debug = Deno.env.get("DEBUG")?.includes("dbx") || config.debug || false;
181+
// if (debug) DB.client = new DebugClient(DB.client);
191182

192183
// Ensure connection?
193184
if (ensure) await DB.ensure();
@@ -220,8 +211,8 @@ export class DB {
220211
return sql.replace(/:[$A-Z_][0-9A-Z_$]*/ig, function (name) {
221212
const exists = Object.hasOwn(objectParameters, name.substring(1));
222213
const value = objectParameters[name.substring(1)];
223-
if (!exists && !safe) throw new Error("Parameter '" + name + "' is not present in parameters ("+JSON.stringify(objectParameters)+")");
224-
if (value === undefined && !safe) throw new Error("Parameter '" + name + "' exists but is undefined in ("+JSON.stringify(objectParameters)+")");
214+
if (!exists && !safe) throw new Error("Parameter '" + name + "' is not present in parameters (" + JSON.stringify(objectParameters) + ")");
215+
if (value === undefined && !safe) throw new Error("Parameter '" + name + "' exists but is undefined in (" + JSON.stringify(objectParameters) + ")");
225216
const isArray = Array.isArray(value);
226217
// If it is an array we need to repeat N times the '?' and append all the values
227218
if (isArray) arrayParameters.push(...value);
@@ -323,42 +314,6 @@ export class DB {
323314
}
324315
}
325316

326-
// Debug Client
327-
// deno-fmt-ignore
328-
const RESERVED = ["ACCESSIBLE","ADD","ALL","ALTER","ANALYZE","AND","AS","ASC","ASENSITIVE","BEFORE","BETWEEN","BIGINT","BINARY","BLOB","BOTH","BY","CALL","CASCADE","CASE","CHANGE","CHAR","CHARACTER","CHECK","COLLATE","COLUMN","CONDITION","CONSTRAINT","CONTINUE","CONVERT","CREATE","CROSS","CUBE","CUME_DIST","CURRENT_DATE","CURRENT_TIME","CURRENT_TIMESTAMP","CURRENT_USER","CURSOR","DATABASE","DATABASES","DAY_HOUR","DAY_MICROSECOND","DAY_MINUTE","DAY_SECOND","DEC","DECIMAL","DECLARE","DEFAULT","DELAYED","DELETE","DENSE_RANK","DESC","DESCRIBE","DETERMINISTIC","DISTINCT","DISTINCTROW","DIV","DOUBLE","DROP","DUAL","EACH","ELSE","ELSEIF","EMPTY","ENCLOSED","ESCAPED","EXCEPT","EXISTS","EXIT","EXPLAIN","FALSE","FETCH","FIRST_VALUE","FLOAT","FLOAT4","FLOAT8","FOR","FORCE","FOREIGN","FROM","FULLTEXT","FUNCTION","GENERATED","GET","GRANT","GROUP","GROUPING","GROUPS","HAVING","HIGH_PRIORITY","HOUR_MICROSECOND","HOUR_MINUTE","HOUR_SECOND","IF","IGNORE","IN","INDEX","INFILE","INNER","INOUT","INSENSITIVE","INSERT","INT","INT1","INT2","INT3","INT4","INT8","INTEGER","INTERSECT","INTERVAL","INTO","IO_AFTER_GTIDS","IO_BEFORE_GTIDS","IS","ITERATE","JOIN","JSON_TABLE","KEY","KEYS","KILL","LAG","LAST_VALUE","LATERAL","LEAD","LEADING","LEAVE","LEFT","LIKE","LIMIT","LINEAR","LINES","LOAD","LOCALTIME","LOCALTIMESTAMP","LOCK","LONG","LONGBLOB","LONGTEXT","LOOP","LOW_PRIORITY","MASTER_BIND","MASTER_SSL_VERIFY_SERVER_CERT","MATCH","MAXVALUE","MEDIUMBLOB","MEDIUMINT","MEDIUMTEXT","MIDDLEINT","MINUTE_MICROSECOND","MINUTE_SECOND","MOD","MODIFIES","NATURAL","NOT","NO_WRITE_TO_BINLOG","NTH_VALUE","NTILE","NULL","NUMERIC","OF","ON","OPTIMIZE","OPTIMIZER_COSTS","OPTION","OPTIONALLY","OR","ORDER","OUT","OUTER","OUTFILE","OVER","PARTITION","PERCENT_RANK","PRECISION","PRIMARY","PROCEDURE","PURGE","RANGE","RANK","READ","READS","READ_WRITE","REAL","RECURSIVE","REFERENCES","REGEXP","RELEASE","RENAME","REPEAT","REPLACE","REQUIRE","RESIGNAL","RESTRICT","RETURN","REVOKE","RIGHT","RLIKE","ROW","ROWS","ROW_NUMBER","SCHEMA","SCHEMAS","SECOND_MICROSECOND","SELECT","SENSITIVE","SEPARATOR","SET","SHOW","SIGNAL","SMALLINT","SPATIAL","SPECIFIC","SQL","SQLEXCEPTION","SQLSTATE","SQLWARNING","SQL_BIG_RESULT","SQL_CALC_FOUND_ROWS","SQL_SMALL_RESULT","SSL","STARTING","STORED","STRAIGHT_JOIN","SYSTEM","TABLE","TERMINATED","THEN","TINYBLOB","TINYINT","TINYTEXT","TO","TRAILING","TRIGGER","TRUE","UNDO","UNION","UNIQUE","UNLOCK","UNSIGNED","UPDATE","USAGE","USE","USING","UTC_DATE","UTC_TIME","UTC_TIMESTAMP","VALUES","VARBINARY","VARCHAR","VARCHARACTER","VARYING","VIRTUAL","WHEN","WHERE","WHILE","WINDOW","WITH","WRITE","XOR","YEAR_MONTH","ZEROFILL"];
329-
330-
class DebugClient implements Client {
331-
config: ClientConfig;
332-
reserved: RegExp;
333-
constructor(private client: Client) {
334-
this.config = client.config;
335-
this.reserved = new RegExp("\\b(" + RESERVED.join("|") + ")\\b", "g");
336-
}
337-
close(): Promise<void> {
338-
return this.client.close();
339-
}
340-
async execute(sql: string, parameters?: Parameter[], debug?: boolean): Promise<{ affectedRows?: number; lastInsertId?: number }> {
341-
const start = Date.now();
342-
const result = await this.client.execute(sql, parameters);
343-
if (debug !== false) this.debug(sql, parameters ?? [], result.affectedRows ?? 0, start);
344-
return result;
345-
}
346-
async query(sql: string, parameters?: Parameter[], debug?: boolean): Promise<Row[]> {
347-
const start = Date.now();
348-
const result = await this.client.query(sql, parameters);
349-
if (debug !== false) this.debug(sql, parameters ?? [], result.length ?? 0, start);
350-
return result;
351-
}
352-
debug(sql: string, parameters: Parameter[], rows: number, start: number, indent = "", pad = 20) {
353-
// If the flag is ON will debug to console
354-
const time = "(" + rows + " row" + (rows === 1 ? "" : "s") + " in " + (Date.now() - start) + "ms)";
355-
let i = 0;
356-
sql = sql.replace(/\?/g, () => blue(String(i < parameters.length ? parameters[i++] : "⚠️")));
357-
sql = sql.replace(this.reserved, (w) => bold(w));
358-
console.debug(indent + "🛢️ " + white(time.padStart(pad)) + " " + sql.trim());
359-
}
360-
}
361-
362317
type LocalClientConfig = ClientConfig;
363318
type LocalProvider = Provider;
364319
type LocalSchema = Schema;

src/hub.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import * as colors from "@std/fmt/colors";
2+
3+
/**
4+
* Hub - a spiritual successor to [debug-js](https://github.com/debug-js/debug)
5+
*
6+
* Simplest possible logging utility, wrapping console. Tries to support all of
7+
* debug-js features and is just as opinionated. The less decisions you have to make
8+
* when invoking, the more time you can spend on your actual code.
9+
*
10+
* It supports the following levels to match the console: `debug`, `info`, `warn`, `error`
11+
* and `off`.
12+
*
13+
* It can supplant the `console` object, but it is not recommended to use it in libraries.
14+
* It is useful for when you forget where you put your `console.log` statements and want to
15+
* turn them off quickly.
16+
*
17+
* It never touches `console.log` which is the most common method used in the wild. It does not
18+
* throw exceptions if you pass the wrong log level, but it will have the effect of printing
19+
* everything since the `n` level index will -1.
20+
*
21+
* Differences from `debug-js`:
22+
* - It does not support the `inspect` option
23+
* - It works not just for debug but for all levels
24+
* - It only allows for the ':' separator
25+
*
26+
*/
27+
28+
export const LEVELS: string[] = ["debug", "info", "warn", "error", "off"] as const;
29+
export const ICONS: string[] = [ "🟢", "🔵", "🟡", "🔴", "🔕"] as const;
30+
export const COLORS = [ colors.red, colors.yellow, colors.blue, colors.magenta, colors.cyan] as const;
31+
32+
// Defaults
33+
export const DEFAULTS = {
34+
buffer: undefined as unknown[][] | undefined,
35+
console: globalThis.console,
36+
fileLine: false,
37+
icons: true,
38+
level: "info" as typeof LEVELS[number],
39+
time: true,
40+
};
41+
42+
// Cache of all instances created
43+
const cache = new Map<string, Console & { level: string }>();
44+
45+
// Set of rules to determine whether to enable a debug namespace
46+
const enabled = new Set<string>();
47+
48+
// Utility function color deterministically based on the hash of the namespace (using djb2 XOR version)
49+
// See https://gist.github.com/eplawless/52813b1d8ad9af510d85
50+
function color(ns: string, apply = false, bold = true): string | number {
51+
const hash = (s: string) => [...s].reduce((h, c) => h * 33 ^ c.charCodeAt(0), 5381) >>> 0;
52+
const i = Math.abs(hash(ns)) % COLORS.length;
53+
return apply ? (bold ? colors.bold(COLORS[i](ns)) : COLORS[i](ns)) : i;
54+
}
55+
56+
// Utility function to prefix the output (with namespace, fileLine, etc). We need to do this
57+
// because we want to be 100% compatible with the console object
58+
59+
function parameters(args: unknown[], ns: string, level: number, options: { fileLine?: boolean, icons?: boolean, time?: boolean }): unknown[] {
60+
// Add colors to the namespace (Deno takes care of removing if no TTY?)
61+
let prefix = color(ns, true, true) as string;
62+
63+
// Figure fileLine option(s)
64+
const [f, l] = (options.fileLine ? new Error().stack?.split("\n")[3].split("/").pop()?.split(":"): []) as string[];
65+
if (options.fileLine) prefix = colors.underline(colors.white("[" + f + ":" + l + "]")) + " " + prefix;
66+
67+
// Should we add icons?
68+
if (options.icons) prefix = ICONS[level] + " " + prefix;
69+
70+
// Organize parameters
71+
args = typeof args.at(0) === "string" ? [prefix + " " + args.shift(), ...args] : [prefix, ...args];
72+
73+
// Add to buffer
74+
const length = DEFAULTS.buffer ? DEFAULTS.buffer.push([LEVELS[level], args]) : 0;
75+
if (length > 1000) throw new Error("Buffer is just meant for tests. If it has grown beyond '1,000' it probably means that you left it on by mistake.");
76+
77+
// Should we add time?
78+
if (options.time) args.push(COLORS[color(ns) as number]("+" + performance.measure(ns).duration.toFixed(2).toString()+"ms"));
79+
80+
return args;
81+
}
82+
83+
/**
84+
* Function to setup flags based on DEBUG environment variable. Used to enable
85+
* based on space or comma-delimited names.
86+
*
87+
* @param debug - debug string
88+
*/
89+
export function setup(debug = Deno.env.get("DEBUG") ?? ""): void {
90+
// Empty set, add all elements and return a copy
91+
enabled.clear();
92+
for (const ns of debug.split(/[\s,]+/)) enabled.add(ns);
93+
}
94+
95+
/**
96+
* Creates a dash object (which you can think of as a soup-up console)
97+
* @param ns - Namespace, which is the name of the logger
98+
* @param level - Level of logging
99+
* @param options - behavior modifiers
100+
* @returns - extended console
101+
*/
102+
export function hub(ns: string, level?: typeof LEVELS[number]): Console & { level: string } {
103+
// Has it been created before? Only use cache if we are not changing options
104+
let instance = cache.get(ns);
105+
if (instance && level) instance.level = level;
106+
if (instance) return instance as Console & { level: string };
107+
108+
// If we have not passed an *explicit* level and the namespace is enabled, set it to debug
109+
level ??= (enabled.has(ns) || enabled.has("*") ? "debug" : DEFAULTS.level);
110+
111+
// The outside world should never have access to `n`
112+
let n = LEVELS.indexOf(level), time = Date.now();
113+
114+
// Create initial instance before decorating it with console methods
115+
performance.mark(ns);
116+
instance = { get level() { return LEVELS[n]; }, set level(l: typeof LEVELS[number]) { n = LEVELS.indexOf(l); }, time: Date.now() } as Console & { level: string, time: number };
117+
cache.set(ns, instance);
118+
119+
// Get a pointer to the console to use internally (will be changed for testing)
120+
const c = DEFAULTS.console;
121+
122+
// Add special version of `debug/info/warn/error`
123+
// deno-lint-ignore no-explicit-any
124+
LEVELS.forEach((l, i) => (instance as any)[l] = (...args: unknown[]) => n <= i ? (c as any)[l](...parameters(args, ns, i, DEFAULTS)) : () => {});
125+
126+
// Return completed prototype. It will NOT overwrite the previously defined functions
127+
// NOTE: By deleting the custom object versions we can go back to the prototype versions
128+
return Object.setPrototypeOf(instance, c) as Console & { level: string };
129+
}
130+
131+
// Setup
132+
// setup();
133+
134+
// Export private functions so that we can test
135+
export { color };

src/kv-old.ts

Lines changed: 0 additions & 101 deletions
This file was deleted.

0 commit comments

Comments
 (0)