@primate/mysql 0.6.0 → 0.8.0

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.
@@ -1,24 +1,29 @@
1
- import type { As, DataDict, DB, Schema, Sort, With } from "@primate/core/db";
1
+ import type { As, DataDict, ExtractPlaceholders, Schema, Sort, SQLDB, With, CheckPlaceholders } from "@primate/core/db";
2
2
  import type { Dict } from "@rcompat/type";
3
- declare const schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
3
+ import type { Pool } from "mysql2/promise";
4
+ import type { ObjectType, Parsed } from "pema";
5
+ declare const schema: ObjectType<import("pema").NormalizeSchemaObject<{
4
6
  readonly host: import("pema").DefaultType<import("pema").StringType, "localhost">;
5
- readonly port: import("pema").DefaultType<import("pema").UintType<"u32">, 3306>;
7
+ readonly port: import("pema").DefaultType<import("pema").UintType<"u32", undefined>, 3306>;
6
8
  readonly database: import("pema").StringType;
7
9
  readonly password: import("pema").OptionalType<import("pema").StringType>;
8
10
  readonly username: import("pema").OptionalType<import("pema").StringType>;
9
- }>, {
11
+ readonly jsonStrings: import("pema").DefaultType<import("pema").BooleanType<undefined>, true>;
12
+ }>, undefined, {
10
13
  host: string;
11
14
  port: number;
12
15
  database: string;
13
16
  password: string | undefined;
14
17
  username: string | undefined;
18
+ jsonStrings: boolean;
15
19
  }>;
16
- export default class MySQL implements DB {
20
+ export default class MySQL implements SQLDB<Pool> {
17
21
  #private;
18
22
  static config: typeof schema.input;
19
23
  constructor(config?: typeof schema.input, options?: {
20
24
  debug?: boolean;
21
25
  });
26
+ get client(): Pool;
22
27
  get explain(): Dict<{
23
28
  query: string;
24
29
  plans: string[];
@@ -45,6 +50,13 @@ export default class MySQL implements DB {
45
50
  delete(as: As, args: {
46
51
  where: DataDict;
47
52
  }): Promise<number>;
53
+ sql<Q extends string, I extends ObjectType<{
54
+ [K in ExtractPlaceholders<Q>]: Parsed<unknown>;
55
+ }> | undefined, O extends Parsed<unknown> | undefined>(options: {
56
+ input?: I extends ObjectType<any> ? CheckPlaceholders<I, Q> : I;
57
+ query: Q;
58
+ output?: O;
59
+ }): (args: I extends ObjectType<any> ? I["infer"] : void) => Promise<O extends Parsed<unknown> ? O["infer"] : void>;
48
60
  }
49
61
  export {};
50
62
  //# sourceMappingURL=MySQL.d.ts.map
@@ -15,14 +15,14 @@ function is_bigint_key(k) {
15
15
  return k === "u64" || k === "u128" || k === "i64" || k === "i128";
16
16
  }
17
17
  function order_by(types, sort, alias) {
18
- if (sort === undefined)
18
+ if (is.undefined(sort))
19
19
  return "";
20
20
  const entries = Object.entries(sort);
21
21
  if (entries.length === 0)
22
22
  return "";
23
23
  const parts = entries.map(([k, dir]) => {
24
24
  const quoted = sql.quote(k);
25
- const base = alias !== undefined ? `${alias}.${quoted}` : quoted;
25
+ const base = is.defined(alias) ? `${alias}.${quoted}` : quoted;
26
26
  const expression = is_bigint_key(types[k]) ? bigint_cast(base) : base;
27
27
  return `${expression} ${dir.toLowerCase() === "desc" ? "DESC" : "ASC"}`;
28
28
  });
@@ -78,6 +78,7 @@ const schema = p({
78
78
  database: p.string,
79
79
  password: p.string.optional(),
80
80
  username: p.string.optional(),
81
+ jsonStrings: p.boolean.default(true),
81
82
  });
82
83
  export default class MySQL {
83
84
  static config;
@@ -96,12 +97,16 @@ export default class MySQL {
96
97
  namedPlaceholders: true,
97
98
  bigNumberStrings: true,
98
99
  supportBigNumbers: true,
100
+ jsonStrings: parsed.jsonStrings,
99
101
  });
100
102
  this.#debug = options?.debug ?? false;
101
103
  }
102
104
  get #db() {
103
105
  return this.#client ??= this.#factory();
104
106
  }
107
+ get client() {
108
+ return this.#db;
109
+ }
105
110
  async #sql(query, params) {
106
111
  const [rows] = await this.#db.query(query, params);
107
112
  return rows;
@@ -208,7 +213,9 @@ export default class MySQL {
208
213
  const numeric = is_bigint_key(datatype);
209
214
  const lhs = numeric ? bigint_cast(quoted) : quoted;
210
215
  const rhs = numeric ? bigint_cast(ph) : ph;
211
- binds[bind_key] = await bind_value(datatype, op_value);
216
+ if (op !== "$in") {
217
+ binds[bind_key] = await bind_value(datatype, op_value);
218
+ }
212
219
  switch (op) {
213
220
  case "$like":
214
221
  parts.push(`${quoted} LIKE BINARY ${ph}`);
@@ -229,6 +236,19 @@ export default class MySQL {
229
236
  parts.push(`${lhs} ${sql_op} ${rhs}`);
230
237
  break;
231
238
  }
239
+ case "$in": {
240
+ const array = op_value;
241
+ const placeholders = [];
242
+ for (let i = 0; i < array.length; i++) {
243
+ const key = `${sql.bindKey(field, `in${i}`)}`;
244
+ binds[key] = await bind_value(datatype, array[i]);
245
+ const ph = `${BIND_BY}${key}`;
246
+ placeholders.push(is_bigint_key(datatype) ? bigint_cast(ph) : ph);
247
+ }
248
+ const lhs = is_bigint_key(datatype) ? bigint_cast(quoted) : quoted;
249
+ parts.push(`${lhs} IN (${placeholders.join(", ")})`);
250
+ break;
251
+ }
232
252
  default:
233
253
  throw E.operator_unknown(field, op);
234
254
  }
@@ -340,15 +360,16 @@ export default class MySQL {
340
360
  return Number(rows[0]?.n ?? 0);
341
361
  }
342
362
  async #base_query(as, args) {
343
- const SELECT = args.fields === undefined
363
+ const SELECT = is.undefined(args.fields)
344
364
  ? "*"
345
365
  : args.fields.map(sql.quote).join(", ");
346
366
  const [WHERE, binds] = await this.#where(as, args.where);
347
367
  const ORDER_BY = order_by(as.types, args.sort);
348
- const LIMIT = sql.limit(args.limit);
368
+ const LIMIT = is.defined(args.limit) ? ` LIMIT ${args.limit}` : "";
369
+ const OFFSET = is.defined(args.offset) ? ` OFFSET ${args.offset}` : "";
349
370
  const table = sql.quote(as.table);
350
371
  return [
351
- `SELECT ${SELECT} FROM ${table} ${WHERE}${ORDER_BY}${LIMIT}`,
372
+ `SELECT ${SELECT} FROM ${table} ${WHERE}${ORDER_BY}${LIMIT}${OFFSET}`,
352
373
  binds,
353
374
  ];
354
375
  }
@@ -424,7 +445,7 @@ export default class MySQL {
424
445
  : [in_part];
425
446
  const WHERE = `WHERE ${where_parts.join(" AND ")}`;
426
447
  const all_columns = Object.keys(args.as.types);
427
- const fields = args.fields !== undefined && args.fields.length > 0
448
+ const fields = is.defined(args.fields) && is.uint(args.fields.length)
428
449
  ? args.fields
429
450
  : all_columns;
430
451
  const select_fields = [...new Set([...fields, args.by])];
@@ -437,7 +458,7 @@ export default class MySQL {
437
458
  : ` ORDER BY ${base_order}`;
438
459
  const table = sql.quote(args.as.table);
439
460
  let query;
440
- if (per_parent !== undefined) {
461
+ if (is.defined(per_parent)) {
441
462
  const partition = numeric ? cast : Q `${args.by}`;
442
463
  const rn_order = user_order ? ` ORDER BY ${user_order}` : "";
443
464
  query = `
@@ -524,5 +545,32 @@ export default class MySQL {
524
545
  const result = await this.#execute(query, binds);
525
546
  return result.affectedRows;
526
547
  }
548
+ sql(options) {
549
+ if (is.defined(options.input)) {
550
+ const placeholders = new Set([...options.query.matchAll(/:(\w+)/g)].map(m => m[1]));
551
+ const input_keys = new Set(Object.keys(options.input.properties));
552
+ for (const key of input_keys) {
553
+ if (!placeholders.has(key))
554
+ throw E.sql_placeholder_missing(key);
555
+ }
556
+ for (const key of placeholders) {
557
+ if (!input_keys.has(key))
558
+ throw E.sql_input_missing(key);
559
+ }
560
+ }
561
+ return async (args) => {
562
+ const input = is.defined(options.input)
563
+ ? options.input.parse(args)
564
+ : undefined;
565
+ const binds = is.defined(input)
566
+ ? await this.#create_params(Object.fromEntries(Object.entries(options.input.properties)
567
+ .map(([k, v]) => [k, v.datatype])), input)
568
+ : {};
569
+ const rows = await this.#sql(options.query, binds);
570
+ return (is.defined(options.output)
571
+ ? options.output.parse(rows)
572
+ : undefined);
573
+ };
574
+ }
527
575
  }
528
576
  //# sourceMappingURL=MySQL.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primate/mysql",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "MySQL databases for Primate",
5
5
  "homepage": "https://primate.run/docs/database/mysql",
6
6
  "bugs": "https://github.com/primate-run/primate/issues",
@@ -17,17 +17,17 @@
17
17
  "!/**/*.spec.*"
18
18
  ],
19
19
  "devDependencies": {
20
- "@rcompat/type": "^0.11.1"
20
+ "@rcompat/type": "^0.17.0"
21
21
  },
22
22
  "dependencies": {
23
- "@rcompat/assert": "^0.8.1",
24
- "@rcompat/is": "^0.6.1",
25
- "mysql2": "^3.20.0",
26
- "@primate/core": "^0.6.0",
27
- "pema": "0.6.0"
23
+ "@rcompat/assert": "^0.13.0",
24
+ "@rcompat/is": "^0.11.0",
25
+ "mysql2": "^3.22.4",
26
+ "@primate/core": "^0.8.0",
27
+ "pema": "0.8.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "primate": "^0.37.0"
30
+ "primate": "^0.39.0"
31
31
  },
32
32
  "imports": {
33
33
  "#*": {
@@ -42,9 +42,13 @@
42
42
  }
43
43
  },
44
44
  "scripts": {
45
- "build": "npm run clean && tsc",
45
+ "build": "npm run clean && tsgo",
46
46
  "clean": "rm -rf ./lib",
47
47
  "lint": "eslint .",
48
- "test": "npx proby"
48
+ "mysql": "scripts/mysql.sh",
49
+ "mariadb": "scripts/mariadb.sh",
50
+ "test:mysql": "npm run mysql && npx proby",
51
+ "test:mariadb": "npm run mariadb && npx proby",
52
+ "test": "npm run test:mysql"
49
53
  }
50
54
  }