@primate/mysql 0.5.0 → 0.6.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.
@@ -20,6 +20,8 @@ type ColumnTypes = Validate<{
20
20
  "TIME": string;
21
21
  TINYINT: number;
22
22
  "TINYINT UNSIGNED": number;
23
+ JSON: string;
24
+ "BINARY(16)": Uint8Array<ArrayBuffer>;
23
25
  }>;
24
26
  export type { ColumnTypes as default };
25
27
  //# sourceMappingURL=ColumnTypes.d.ts.map
@@ -1,12 +1,17 @@
1
- import type { As, DataDict, DB, Sort, With } from "@primate/core/db";
1
+ import type { As, DataDict, DB, Schema, Sort, With } from "@primate/core/db";
2
2
  import type { Dict } from "@rcompat/type";
3
- import type { StoreSchema } from "pema";
4
- declare const schema: import("pema").ObjectType<{
5
- database: import("pema").StringType;
6
- host: import("pema").DefaultType<import("pema").StringType, "localhost">;
7
- password: import("pema").OptionalType<import("pema").StringType>;
8
- port: import("pema").DefaultType<import("pema").UintType<"u32">, 3306>;
9
- username: import("pema").OptionalType<import("pema").StringType>;
3
+ declare const schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
4
+ readonly host: import("pema").DefaultType<import("pema").StringType, "localhost">;
5
+ readonly port: import("pema").DefaultType<import("pema").UintType<"u32">, 3306>;
6
+ readonly database: import("pema").StringType;
7
+ readonly password: import("pema").OptionalType<import("pema").StringType>;
8
+ readonly username: import("pema").OptionalType<import("pema").StringType>;
9
+ }>, {
10
+ host: string;
11
+ port: number;
12
+ database: string;
13
+ password: string | undefined;
14
+ username: string | undefined;
10
15
  }>;
11
16
  export default class MySQL implements DB {
12
17
  #private;
@@ -19,10 +24,7 @@ export default class MySQL implements DB {
19
24
  plans: string[];
20
25
  }>;
21
26
  close(): Promise<void>;
22
- get schema(): {
23
- create: (as: As, store: StoreSchema) => Promise<void>;
24
- delete: (table: string) => Promise<void>;
25
- };
27
+ get schema(): Schema;
26
28
  create<O extends Dict>(as: As, record: Dict): Promise<O>;
27
29
  read(as: As, args: {
28
30
  count: true;
@@ -1,6 +1,6 @@
1
1
  import typemap from "#typemap";
2
- import common from "@primate/core/db";
3
- import E from "@primate/core/db/error";
2
+ import base from "@primate/core/db";
3
+ import E from "@primate/core/db/errors";
4
4
  import sql from "@primate/core/db/sql";
5
5
  import assert from "@rcompat/assert";
6
6
  import is from "@rcompat/is";
@@ -22,7 +22,7 @@ function order_by(types, sort, alias) {
22
22
  return "";
23
23
  const parts = entries.map(([k, dir]) => {
24
24
  const quoted = sql.quote(k);
25
- const base = alias ? `${alias}.${quoted}` : quoted;
25
+ const base = alias !== undefined ? `${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
  });
@@ -42,11 +42,41 @@ function unbind_value(key, value) {
42
42
  function unbind(types, row) {
43
43
  return sql.unbind(types, row, unbind_value);
44
44
  }
45
+ function columns_to_types(data_t, column_t) {
46
+ const unsigned = column_t.includes("unsigned");
47
+ if (data_t === "varchar")
48
+ return ["string", "url"];
49
+ if (data_t === "text")
50
+ return ["string", "url", "time"];
51
+ if (data_t === "binary" && column_t.includes("(16)"))
52
+ return base.UUID_TYPES;
53
+ if (data_t === "smallint")
54
+ return unsigned ? ["u16"] : ["i16"];
55
+ if (data_t === "int")
56
+ return unsigned ? ["u32"] : ["i32"];
57
+ if (data_t === "bigint")
58
+ return unsigned ? ["u64"] : ["i64"];
59
+ if (data_t === "decimal")
60
+ return ["u128", "i128"];
61
+ if (data_t === "double")
62
+ return ["f32", "f64"];
63
+ if (data_t === "datetime")
64
+ return ["datetime"];
65
+ if (data_t === "tinyint" && column_t === "tinyint(1)")
66
+ return ["boolean"];
67
+ if (data_t === "tinyint")
68
+ return unsigned ? ["u8"] : ["i8", "boolean"];
69
+ if (data_t === "blob")
70
+ return ["blob"];
71
+ if (data_t === "json")
72
+ return ["json"];
73
+ return [];
74
+ }
45
75
  const schema = p({
46
- database: p.string,
47
76
  host: p.string.default("localhost"),
48
- password: p.string.optional(),
49
77
  port: p.uint.port().default(3306),
78
+ database: p.string,
79
+ password: p.string.optional(),
50
80
  username: p.string.optional(),
51
81
  });
52
82
  export default class MySQL {
@@ -94,26 +124,63 @@ export default class MySQL {
94
124
  }
95
125
  get schema() {
96
126
  return {
97
- create: async (as, store) => {
127
+ create: async (table, pk, types) => {
98
128
  const columns = [];
99
- for (const [key, value] of Object.entries(store)) {
100
- const type = get_column(value.datatype);
101
- if (key === as.pk) {
102
- const is_int = common.INT_TYPES.includes(value.datatype);
103
- const auto = as.generate_pk && is_int ? " AUTO_INCREMENT" : "";
129
+ for (const [key, value] of Object.entries(types)) {
130
+ const type = get_column(value);
131
+ if (key === pk.name) {
132
+ const is_int = base.INT_TYPES.includes(value);
133
+ const auto = pk.generate && is_int ? " AUTO_INCREMENT" : "";
104
134
  // 36 is UUID length
105
- const pk_type = value.datatype === "string" ? "VARCHAR(36)" : type;
135
+ const pk_type = value === "string" ? "VARCHAR(36)" : type;
106
136
  columns.push(`${sql.quote(key)} ${pk_type} PRIMARY KEY${auto}`);
107
137
  }
108
138
  else {
109
139
  columns.push(`${sql.quote(key)} ${type}`);
110
140
  }
111
141
  }
112
- await this.#sql(Q `CREATE TABLE IF NOT EXISTS ${as.table} (${columns})`);
142
+ await this.#sql(Q `CREATE TABLE IF NOT EXISTS ${table} (${columns})`);
113
143
  },
114
144
  delete: async (table) => {
115
145
  await this.#sql(Q `DROP TABLE IF EXISTS ${table}`);
116
146
  },
147
+ introspect: async (table) => {
148
+ const rows = await this.#sql(`
149
+ SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE
150
+ FROM information_schema.COLUMNS
151
+ WHERE TABLE_SCHEMA = DATABASE()
152
+ AND TABLE_NAME = :name
153
+ `, { name: table });
154
+ if (rows.length === 0)
155
+ return null;
156
+ const result = {};
157
+ for (const row of rows) {
158
+ const candidates = columns_to_types(row.DATA_TYPE, row.COLUMN_TYPE);
159
+ if (candidates.length > 0)
160
+ result[row.COLUMN_NAME] = candidates;
161
+ }
162
+ return result;
163
+ },
164
+ alter: async (table, diff) => {
165
+ const existing = await this.schema.introspect(table);
166
+ if (existing === null)
167
+ throw E.table_not_found(table);
168
+ for (const [from, to] of diff.rename) {
169
+ await this.#sql(Q `
170
+ ALTER TABLE ${table}
171
+ RENAME COLUMN ${[sql.quote(from)]} TO ${[sql.quote(to)]}`);
172
+ }
173
+ const parts = [];
174
+ for (const field of diff.drop) {
175
+ parts.push(`DROP COLUMN ${sql.quote(field)}`);
176
+ }
177
+ for (const [field, type] of Object.entries(diff.add)) {
178
+ parts.push(`ADD COLUMN ${sql.quote(field)} ${get_column(type)}`);
179
+ }
180
+ if (parts.length > 0) {
181
+ await this.#sql(Q `ALTER TABLE ${table} ${[parts.join(", ")]}`);
182
+ }
183
+ },
117
184
  };
118
185
  }
119
186
  async #where(as, where) {
@@ -193,9 +260,9 @@ export default class MySQL {
193
260
  const pk = as.pk;
194
261
  const type = as.types[pk];
195
262
  const table = as.table;
196
- if (type === "string")
197
- return crypto.randomUUID();
198
- if (common.BIGINT_STRING_TYPES.includes(type)) {
263
+ if (base.is_uuid_type(type))
264
+ return base.generate_uuid(type);
265
+ if (base.BIGINT_STRING_TYPES.includes(type)) {
199
266
  const cast = bigint_cast(sql.quote(pk));
200
267
  const q = `SELECT MAX(${cast}) AS v FROM ${sql.quote(table)}`;
201
268
  const rows = await this.#sql(q);
@@ -235,7 +302,7 @@ export default class MySQL {
235
302
  throw E.pk_required(pk);
236
303
  const type = as.types[pk];
237
304
  // integer types, use AUTO_INCREMENT
238
- if (!common.BIGINT_STRING_TYPES.includes(type) && type !== "string") {
305
+ if (!base.BIGINT_STRING_TYPES.includes(type) && !base.is_uuid_type(type)) {
239
306
  const [keys, values] = this.#create(record);
240
307
  const query = keys.length > 0
241
308
  ? Q `INSERT INTO ${table} (${keys}) VALUES (${values})`
@@ -257,7 +324,7 @@ export default class MySQL {
257
324
  this.#explain = {};
258
325
  if (args.count === true)
259
326
  return this.#count(as, args.where);
260
- if (common.withed(args)) {
327
+ if (base.withed(args)) {
261
328
  return sql.joinable(as, args.with)
262
329
  ? this.#read_joined(as, args)
263
330
  : this.#read_phased(as, args);
@@ -292,9 +359,9 @@ export default class MySQL {
292
359
  return rows.map(r => unbind(as.types, r));
293
360
  }
294
361
  async #read_phased(as, args) {
295
- const fields = common.expand(as, args.fields, args.with);
362
+ const fields = base.expand(as, args.fields, args.with);
296
363
  const rows = await this.#read(as, { ...args, fields });
297
- const out = rows.map(row => common.project(row, args.fields));
364
+ const out = rows.map(row => base.project(row, args.fields));
298
365
  for (const [table, relation] of Object.entries(args.with)) {
299
366
  await this.#attach_relation(as, { rows, out, table, relation });
300
367
  }
@@ -330,8 +397,8 @@ export default class MySQL {
330
397
  }
331
398
  const rows = grouped.get(join_value) ?? [];
332
399
  args.out[i][args.table] = is_many
333
- ? rows.map(r => common.project(r, relation.fields))
334
- : rows[0] ? common.project(rows[0], relation.fields) : null;
400
+ ? rows.map(r => base.project(r, relation.fields))
401
+ : rows[0] ? base.project(rows[0], relation.fields) : null;
335
402
  }
336
403
  }
337
404
  async #load_related(args) {
@@ -416,13 +483,13 @@ export default class MySQL {
416
483
  const tables = [as.table, ...Object.values(args.with).map(r => r.as.table)];
417
484
  const aliases = sql.aliases(tables);
418
485
  const alias = aliases[as.table];
419
- const fields = common.fields(args.fields, as.pk) ?? Object.keys(as.types);
486
+ const fields = base.fields(args.fields, as.pk) ?? Object.keys(as.types);
420
487
  const SELECT = [
421
488
  ...fields.map(f => `${alias}.${sql.quote(f)} AS ${alias}_${f}`),
422
489
  ...Object.values(args.with).flatMap(relation => {
423
490
  const r_alias = aliases[relation.as.table];
424
491
  const r_fields = relation.as.pk !== null
425
- ? common.fields(relation.fields, relation.as.pk)
492
+ ? base.fields(relation.fields, relation.as.pk)
426
493
  : relation.fields;
427
494
  return (r_fields ?? Object.keys(relation.as.types))
428
495
  .map(f => `${r_alias}.${sql.quote(f)} AS ${r_alias}_${f}`);
@@ -12,6 +12,18 @@ function number(column) {
12
12
  unbind: (value) => Number(value),
13
13
  };
14
14
  }
15
+ function uuid_to_bin(uuid) {
16
+ return Uint8Array.fromHex(uuid.replace(/-/g, ""));
17
+ }
18
+ function bin_to_uuid(buf) {
19
+ const hex = buf.toHex();
20
+ const a = hex.slice(0, 8);
21
+ const b = hex.slice(8, 12);
22
+ const c = hex.slice(12, 16);
23
+ const d = hex.slice(16, 20);
24
+ const e = hex.slice(20);
25
+ return `${a}-${b}-${c}-${d}-${e}`;
26
+ }
15
27
  const typemap = {
16
28
  blob: {
17
29
  async bind(value) {
@@ -55,16 +67,15 @@ const typemap = {
55
67
  },
56
68
  },
57
69
  i8: number("TINYINT"),
58
- /*primary: {
59
- bind(value) {
60
- if (is.numeric(value)) return Number(value);
61
- throw new Error(`\`${value}\` is not a valid primary key value`);
62
- },
63
- column: "INT NOT NULL AUTO_INCREMENT PRIMARY KEY",
64
- unbind(value) {
65
- return String(value);
66
- },
67
- },*/
70
+ json: {
71
+ bind(value) {
72
+ return JSON.stringify(value);
73
+ },
74
+ column: "JSON",
75
+ unbind(value) {
76
+ return JSON.parse(value);
77
+ },
78
+ },
68
79
  string: identity("TEXT"),
69
80
  time: identity("TEXT"),
70
81
  u128: {
@@ -97,6 +108,21 @@ const typemap = {
97
108
  return new URL(value);
98
109
  },
99
110
  },
111
+ uuid: {
112
+ bind: uuid_to_bin,
113
+ column: "BINARY(16)",
114
+ unbind: bin_to_uuid,
115
+ },
116
+ uuid_v4: {
117
+ bind: uuid_to_bin,
118
+ column: "BINARY(16)",
119
+ unbind: bin_to_uuid,
120
+ },
121
+ uuid_v7: {
122
+ bind: uuid_to_bin,
123
+ column: "BINARY(16)",
124
+ unbind: bin_to_uuid,
125
+ },
100
126
  };
101
127
  export default typemap;
102
128
  //# sourceMappingURL=typemap.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primate/mysql",
3
- "version": "0.5.0",
3
+ "version": "0.6.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,27 +17,27 @@
17
17
  "!/**/*.spec.*"
18
18
  ],
19
19
  "devDependencies": {
20
- "@rcompat/type": "^0.9.0"
20
+ "@rcompat/type": "^0.11.1"
21
21
  },
22
22
  "dependencies": {
23
- "@rcompat/assert": "^0.6.0",
24
- "@rcompat/is": "^0.4.2",
25
- "mysql2": "^3.17.1",
26
- "@primate/core": "^0.5.0",
27
- "pema": "0.5.0"
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"
28
28
  },
29
29
  "peerDependencies": {
30
- "primate": "^0.36.0"
30
+ "primate": "^0.37.0"
31
31
  },
32
32
  "imports": {
33
33
  "#*": {
34
- "apekit": "./src/private/*.ts",
34
+ "@primate/source": "./src/private/*.ts",
35
35
  "default": "./lib/private/*.js"
36
36
  }
37
37
  },
38
38
  "exports": {
39
39
  ".": {
40
- "apekit": "./src/public/index.ts",
40
+ "@primate/source": "./src/public/index.ts",
41
41
  "default": "./lib/public/index.js"
42
42
  }
43
43
  },
@@ -45,6 +45,6 @@
45
45
  "build": "npm run clean && tsc",
46
46
  "clean": "rm -rf ./lib",
47
47
  "lint": "eslint .",
48
- "test": "npm run build && npx proby"
48
+ "test": "npx proby"
49
49
  }
50
50
  }