sql-love 1.0.2 → 1.0.4

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.
Files changed (56) hide show
  1. package/README.md +52 -32
  2. package/dist/index.cjs +16 -0
  3. package/dist/index.d.ts +732 -9
  4. package/dist/index.mjs +16 -0
  5. package/package.json +29 -12
  6. package/.github/workflows/node.js.yml +0 -37
  7. package/.prettierrc.json +0 -13
  8. package/CHANGELOG.md +0 -39
  9. package/CONTRIBUTING.md +0 -85
  10. package/assets/sql-love-logo.png +0 -0
  11. package/codecov.yml +0 -7
  12. package/dist/index.js +0 -9
  13. package/dist/src/Parser/Parser.d.ts +0 -157
  14. package/dist/src/Parser/Parser.js +0 -304
  15. package/dist/src/SelectBuilder/SelectBuilder.d.ts +0 -379
  16. package/dist/src/SelectBuilder/SelectBuilder.js +0 -810
  17. package/dist/src/extractGrouped/extractGrouped.d.ts +0 -17
  18. package/dist/src/extractGrouped/extractGrouped.js +0 -31
  19. package/dist/src/extractIndexed/extractIndexed.d.ts +0 -12
  20. package/dist/src/extractIndexed/extractIndexed.js +0 -23
  21. package/dist/src/extractLookup/extractLookup.d.ts +0 -9
  22. package/dist/src/extractLookup/extractLookup.js +0 -21
  23. package/dist/src/getPagination/getPagination.d.ts +0 -16
  24. package/dist/src/getPagination/getPagination.js +0 -40
  25. package/dist/src/runMysql/runMysql.d.ts +0 -58
  26. package/dist/src/runMysql/runMysql.js +0 -61
  27. package/dist/src/runPrisma/runPrisma.d.ts +0 -41
  28. package/dist/src/runPrisma/runPrisma.js +0 -34
  29. package/dist/src/substrCount/substrCount.d.ts +0 -6
  30. package/dist/src/substrCount/substrCount.js +0 -14
  31. package/dist/src/toSafeJson/toSafeJson.d.ts +0 -25
  32. package/dist/src/toSafeJson/toSafeJson.js +0 -45
  33. package/index.ts +0 -19
  34. package/src/Parser/Parser.spec.ts +0 -226
  35. package/src/Parser/Parser.ts +0 -325
  36. package/src/SelectBuilder/SelectBuilder.spec.ts +0 -785
  37. package/src/SelectBuilder/SelectBuilder.ts +0 -914
  38. package/src/extractGrouped/extractGrouped.spec.ts +0 -43
  39. package/src/extractGrouped/extractGrouped.ts +0 -36
  40. package/src/extractIndexed/extractIndexed.spec.ts +0 -40
  41. package/src/extractIndexed/extractIndexed.ts +0 -28
  42. package/src/extractLookup/extractLookup.spec.ts +0 -38
  43. package/src/extractLookup/extractLookup.ts +0 -33
  44. package/src/getPagination/getPagination.spec.ts +0 -75
  45. package/src/getPagination/getPagination.ts +0 -42
  46. package/src/runMysql/runMysql.spec.ts +0 -286
  47. package/src/runMysql/runMysql.ts +0 -106
  48. package/src/runPrisma/runPrisma.spec.ts +0 -99
  49. package/src/runPrisma/runPrisma.ts +0 -75
  50. package/src/substrCount/substrCount.spec.ts +0 -29
  51. package/src/substrCount/substrCount.ts +0 -14
  52. package/src/toSafeJson/toSafeJson.spec.ts +0 -28
  53. package/src/toSafeJson/toSafeJson.ts +0 -56
  54. package/tsconfig.build.json +0 -10
  55. package/tsconfig.json +0 -22
  56. package/vitest.config.ts +0 -11
@@ -1,106 +0,0 @@
1
- import type SelectBuilder from '../SelectBuilder/SelectBuilder';
2
- import type { EngineStyle } from '../SelectBuilder/SelectBuilder';
3
- import getPagination from '../getPagination/getPagination';
4
-
5
- interface MysqlClient {
6
- query: (
7
- sql: string,
8
- bindings: any[],
9
- callback: (
10
- err: Error | null,
11
- results: Array<Record<string, any>>,
12
- fields: any[]
13
- ) => void
14
- ) => void;
15
- }
16
-
17
- interface MysqlAsyncClient {
18
- query: (
19
- sql: string,
20
- bindings: any[]
21
- ) => Promise<[Array<Record<string, any>>, any[]]>;
22
- }
23
-
24
- function _queryToPromise(
25
- mysqlClient: MysqlClient,
26
- sql: string,
27
- bindings: any[]
28
- ): Promise<[Array<Record<string, any>>, any[]]> {
29
- return new Promise((resolve, reject) => {
30
- mysqlClient.query(sql, bindings, (err, rows, fields) => {
31
- if (err) {
32
- reject(err);
33
- return;
34
- }
35
- resolve([rows, fields]);
36
- });
37
- });
38
- }
39
-
40
- /**
41
- * Run the given query with mysql and return the results
42
- * @param mysqlClient A client created with mysql.createConnection() or mysql.createPool()
43
- * @param query The SelectBuilder query to run
44
- */
45
- export async function runMysql(
46
- mysqlClient: MysqlClient,
47
- query: SelectBuilder
48
- ): Promise<[Array<Record<string, any>>, any[]]> {
49
- const { sql, bindings } = query.compile({ engine: 'mysql' });
50
- return _queryToPromise(mysqlClient, sql, bindings);
51
- }
52
-
53
- /**
54
- * Run the given query with mysql and return the results, count, and pagination
55
- * @param mysqlClient A client created with mysql.createConnection() or mysql.createPool()
56
- * @param query The SelectBuilder query to run
57
- * @param options
58
- * @param options.countExpr The SQL expression to use for the count (default "*")
59
- */
60
- export async function runMysqlWithCount(
61
- mysqlClient: MysqlClient,
62
- query: SelectBuilder,
63
- { countExpr = '*' }: { countExpr?: string } = {}
64
- ) {
65
- const { sql, bindings } = query.compile({ engine: 'mysql' });
66
- const [records, fields] = await _queryToPromise(mysqlClient, sql, bindings);
67
- if (records.length > 0) {
68
- const { sql, bindings } = query.compileCount({
69
- engine: 'mysql',
70
- countExpr,
71
- });
72
- const [rows] = await _queryToPromise(mysqlClient, sql, bindings);
73
- const total = rows[0].found_rows;
74
- return { records, total, pagination: getPagination(query, total), fields };
75
- } else {
76
- return { records, total: 0, pagination: getPagination(query, 0), fields };
77
- }
78
- }
79
-
80
- export async function runMysqlAsync(
81
- mysqlClient: MysqlAsyncClient,
82
- query: SelectBuilder
83
- ): Promise<[Array<Record<string, any>>, any[]]> {
84
- const { sql, bindings } = query.compile({ engine: 'mysql' });
85
- return mysqlClient.query(sql, bindings);
86
- }
87
-
88
- export async function runMysqlAsyncWithCount(
89
- mysqlClient: MysqlAsyncClient,
90
- query: SelectBuilder,
91
- {
92
- engine = 'mysql',
93
- countExpr = '*',
94
- }: { engine?: EngineStyle; countExpr?: string } = {}
95
- ) {
96
- const { sql, bindings } = query.compile({ engine });
97
- const [records, fields] = await mysqlClient.query(sql, bindings);
98
- if (records.length > 0) {
99
- const { sql, bindings } = query.compileCount({ engine, countExpr });
100
- const [rows] = await mysqlClient.query(sql, bindings);
101
- const total = rows[0].found_rows;
102
- return { records, total, pagination: getPagination(query, total), fields };
103
- } else {
104
- return { records, total: 0, pagination: getPagination(query, 0), fields };
105
- }
106
- }
@@ -1,99 +0,0 @@
1
- import { vi } from 'vitest';
2
- import SelectBuilder from '../SelectBuilder/SelectBuilder';
3
- import { runPrisma, runPrismaWithCount } from './runPrisma';
4
-
5
- describe('runPrisma', () => {
6
- it('should invoke prisma.$queryRawUnsafe with correct args', async () => {
7
- const mockResults = [
8
- { id: 1, name: 'John', dept: 'Marketing' },
9
- { id: 2, name: 'Jane', dept: 'Marketing' },
10
- ];
11
- const prisma = {
12
- $queryRawUnsafe: vi.fn(() => mockResults),
13
- };
14
- const query = new SelectBuilder('SELECT * FROM users');
15
- query.where('dept', 'Marketing');
16
- // @ts-expect-error
17
- const results = await runPrisma(prisma, query);
18
- expect(prisma.$queryRawUnsafe).toHaveBeenCalledWith(
19
- 'SELECT\n *\nFROM users\nWHERE dept = ?',
20
- 'Marketing'
21
- );
22
- expect(results).toBe(mockResults);
23
- });
24
- it('should also run with count', async () => {
25
- const mockResults1 = [
26
- { id: 1, name: 'John', dept: 'Marketing' },
27
- { id: 2, name: 'Jane', dept: 'Marketing' },
28
- ];
29
- const mockResults2 = [{ found_rows: 9 }];
30
- let callCount = 0;
31
- const prisma = {
32
- $queryRawUnsafe: vi.fn(() => {
33
- if (++callCount === 1) {
34
- return mockResults1;
35
- }
36
- return mockResults2;
37
- }),
38
- };
39
- const query = new SelectBuilder('SELECT * FROM users')
40
- .where('dept', 'Marketing')
41
- .limit(2)
42
- .page(5);
43
- const { records, total, pagination } = await runPrismaWithCount(
44
- // @ts-expect-error
45
- prisma,
46
- query
47
- );
48
- // @ts-expect-error
49
- expect(prisma.$queryRawUnsafe.mock.calls[0][0]).toBe(
50
- 'SELECT\n *\nFROM users\nWHERE dept = ?\nOFFSET 8\nLIMIT 2'
51
- );
52
- // @ts-expect-error
53
- expect(prisma.$queryRawUnsafe.mock.calls[0][1]).toBe('Marketing');
54
- expect(records).toBe(mockResults1);
55
- expect(total).toBe(9);
56
- expect(pagination).toEqual({
57
- page: 5,
58
- prevPage: 4,
59
- nextPage: null,
60
- perPage: 2,
61
- numPages: 5,
62
- total: 9,
63
- isFirst: false,
64
- isLast: true,
65
- });
66
- });
67
- it('should handle when recordset is empty', async () => {
68
- const prisma = {
69
- $queryRawUnsafe: vi.fn(() => []),
70
- };
71
- const query = new SelectBuilder('SELECT * FROM users')
72
- .where('dept', 'Marketing')
73
- .limit(2)
74
- .page(5);
75
- const { records, total, pagination } = await runPrismaWithCount(
76
- // @ts-expect-error
77
- prisma,
78
- query
79
- );
80
- // @ts-expect-error
81
- expect(prisma.$queryRawUnsafe.mock.calls[0][0]).toBe(
82
- 'SELECT\n *\nFROM users\nWHERE dept = ?\nOFFSET 8\nLIMIT 2'
83
- );
84
- // @ts-expect-error
85
- expect(prisma.$queryRawUnsafe.mock.calls[0][1]).toBe('Marketing');
86
- expect(records).toStrictEqual([]);
87
- expect(total).toBe(0);
88
- expect(pagination).toEqual({
89
- page: null,
90
- prevPage: null,
91
- nextPage: null,
92
- perPage: 2,
93
- numPages: 0,
94
- total: 0,
95
- isFirst: false,
96
- isLast: false,
97
- });
98
- });
99
- });
@@ -1,75 +0,0 @@
1
- import type SelectBuilder from '../SelectBuilder/SelectBuilder';
2
- import type { EngineStyle } from '../SelectBuilder/SelectBuilder';
3
- import getPagination from '../getPagination/getPagination';
4
- import { toSafeJsonValue } from '../toSafeJson/toSafeJson';
5
-
6
- interface PrismaClient {
7
- $queryRawUnsafe: (
8
- sql: string,
9
- ...bindings: any[]
10
- ) => Promise<Array<Record<string, any>>>;
11
- }
12
-
13
- /**
14
- * Run the given query with prisma and return the results
15
- * @param prisma The prisma client instance
16
- * @param query The SelectBuilder query to run
17
- * @param options
18
- * @param options.engine The engine style to use ("mssql" | "mysql" | "oracle" | "pg")
19
- */
20
- export async function runPrisma<
21
- T extends Array<Record<string, any>> = Array<Record<string, any>>,
22
- >(
23
- prisma: PrismaClient,
24
- query: SelectBuilder,
25
- { engine = undefined }: { engine?: EngineStyle } = {}
26
- ): Promise<T> {
27
- const { sql, bindings } = query.compile({ engine });
28
- return prisma.$queryRawUnsafe(sql, ...bindings) as unknown as Promise<T>;
29
- }
30
-
31
- /**
32
- * Run the given query with prisma and return the results, count, and pagination
33
- * @param prisma The prisma client instance
34
- * @param query The SelectBuilder query to run
35
- * @param options
36
- * @param options.engine The engine style to use ("mssql" | "mysql" | "oracle" | "pg")
37
- * @param options.countExpr The SQL expression to use for the count (default "*")
38
- */
39
- export async function runPrismaWithCount<
40
- T extends Array<Record<string, any>> = Array<Record<string, any>>,
41
- >(
42
- prisma: PrismaClient,
43
- query: SelectBuilder,
44
- {
45
- engine = undefined,
46
- countExpr = '*',
47
- }: { engine?: EngineStyle; countExpr?: string } = {}
48
- ): Promise<{
49
- records: T;
50
- total: number;
51
- pagination: {
52
- page: number;
53
- prevPage: number;
54
- nextPage: number;
55
- perPage: number;
56
- numPages: number;
57
- total: number;
58
- isFirst: boolean;
59
- isLast: boolean;
60
- };
61
- }> {
62
- const { sql, bindings } = query.compile({ engine });
63
- const records = (await prisma.$queryRawUnsafe(
64
- sql,
65
- ...bindings
66
- )) as unknown as T;
67
- if (records.length > 0) {
68
- const { sql, bindings } = query.compileCount({ engine, countExpr });
69
- const result = await prisma.$queryRawUnsafe(sql, ...bindings);
70
- const total = toSafeJsonValue(result[0].found_rows);
71
- return { records, total, pagination: getPagination(query, total) };
72
- } else {
73
- return { records, total: 0, pagination: getPagination(query, 0) };
74
- }
75
- }
@@ -1,29 +0,0 @@
1
- import substrCount from './substrCount';
2
-
3
- describe('substrCount', function () {
4
- it('should see 0', function () {
5
- const haystack = 'hello world';
6
- const needle = '?';
7
- expect(substrCount(haystack, needle)).toBe(0);
8
- });
9
- it('should see 0 from empty string', function () {
10
- const haystack = '';
11
- const needle = '?';
12
- expect(substrCount(haystack, needle)).toBe(0);
13
- });
14
- it('should see 1', function () {
15
- const haystack = 'hello world?';
16
- const needle = '?';
17
- expect(substrCount(haystack, needle)).toBe(1);
18
- });
19
- it('should see 2 adjacent', function () {
20
- const haystack = 'hello world??';
21
- const needle = '?';
22
- expect(substrCount(haystack, needle)).toBe(2);
23
- });
24
- it('should see 3', function () {
25
- const haystack = '?hello world??';
26
- const needle = '?';
27
- expect(substrCount(haystack, needle)).toBe(3);
28
- });
29
- });
@@ -1,14 +0,0 @@
1
- /**
2
- * Count the number of times a substring appears in a string
3
- * @param haystack
4
- * @param needle
5
- */
6
- export default function substrCount(haystack: string, needle: string) {
7
- let count = 0;
8
- let pos = haystack.indexOf(needle);
9
- while (pos !== -1) {
10
- count += 1;
11
- pos = haystack.indexOf(needle, pos + 1);
12
- }
13
- return count;
14
- }
@@ -1,28 +0,0 @@
1
- import { toSafeJsonRecords, toSafeJsonString } from './toSafeJson';
2
-
3
- describe('toSafeJsonString()', () => {
4
- it('should convert small BigInt to Number', () => {
5
- const records = [{ found_rows: 30n }];
6
- const json = toSafeJsonString(records);
7
- expect(json).toBe('[{"found_rows":30}]');
8
- });
9
- it('should convert large BigInt to String', () => {
10
- const myBigInt = BigInt(String(Number.MAX_SAFE_INTEGER) + '0');
11
- const records = [{ found_rows: myBigInt }];
12
- const json = toSafeJsonString(records);
13
- expect(json).toBe(`[{"found_rows":"${myBigInt.toString()}"}]`);
14
- });
15
- });
16
- describe('jsonSafePrisma()', () => {
17
- it('should convert small BigInt to Number', () => {
18
- const records = [{ found_rows: 30n }];
19
- const json = toSafeJsonRecords(records);
20
- expect(json).toStrictEqual([{ found_rows: 30 }]);
21
- });
22
- it('should convert large BigInt to String', () => {
23
- const myBigInt = BigInt(String(Number.MAX_SAFE_INTEGER) + '0');
24
- const records = [{ found_rows: myBigInt }];
25
- const json = toSafeJsonRecords(records);
26
- expect(json).toStrictEqual([{ found_rows: myBigInt.toString() }]);
27
- });
28
- });
@@ -1,56 +0,0 @@
1
- type SafeValue<T> = T extends BigInt ? number | string : T;
2
- type SafeRecord<T> = Record<string, SafeValue<T>>;
3
- type SafeRecords<T> = Array<SafeRecord<T>>;
4
-
5
- /**
6
- * Convert a value to a value that can be safely serialized to JSON
7
- * @param value
8
- */
9
- export function toSafeJsonValue<T>(value: T): SafeValue<T> {
10
- if (typeof value === 'bigint') {
11
- const asNumber = Number(value);
12
- // Use double equals instead of triple equals below to check if the BigInt
13
- // is too big to fit in a JS Number.
14
- // If so, then return it as a string, otherwise return it as a Number
15
- // @ts-expect-error
16
- return value == asNumber ? asNumber : value.toString();
17
- }
18
- return value as SafeValue<T>;
19
- }
20
-
21
- /**
22
- * Convert a record to one that can be safely serialized to JSON
23
- * @param record
24
- */
25
- export function toSafeJsonRecord<T extends Record<string, any>>(
26
- record: T
27
- ): SafeRecord<T> {
28
- const newRecord: SafeValue<T> = {} as any;
29
- for (const key in record) {
30
- /* v8 ignore next 3 */
31
- if (!Object.prototype.hasOwnProperty.call(record, key)) {
32
- continue;
33
- }
34
- newRecord[key] = toSafeJsonValue(record[key]);
35
- }
36
- return newRecord;
37
- }
38
-
39
- /**
40
- * Convert a recordset to one that can be safely serialized to JSON
41
- * @param records
42
- */
43
- export function toSafeJsonRecords<T extends Record<string, any>>(
44
- records: T[]
45
- ): SafeRecords<T> {
46
- return records.map(toSafeJsonRecord);
47
- }
48
-
49
- /**
50
- * JSON stringify a value or data structure, ensuring first that all values are
51
- * safe to serialize
52
- * @param records
53
- */
54
- export function toSafeJsonString(records: Array<Record<string, any>>) {
55
- return JSON.stringify(records, (_, value: any) => toSafeJsonValue(value));
56
- }
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "exclude": [
4
- "node_modules",
5
- "src/**/*.spec.ts",
6
- "src/**/*.type.ts",
7
- "src/**/*.interface.ts",
8
- "src/mocks"
9
- ]
10
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "ESNext",
4
- "target": "ESNext",
5
- "outDir": "./dist",
6
- "declaration": true,
7
- "removeComments": false,
8
- "noEmit": false,
9
- "pretty": true,
10
- "isolatedModules": true,
11
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
12
- "sourceMap": false,
13
- "skipLibCheck": true,
14
- "moduleResolution": "node",
15
- "allowSyntheticDefaultImports": true,
16
- "types": ["vitest/globals"]
17
- },
18
- "include": [
19
- "src/**/*.ts"
20
- ],
21
- "files": ["index.ts"]
22
- }
package/vitest.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ['src/**/*.spec.ts'],
6
- coverage: {
7
- exclude: ['index.ts'],
8
- },
9
- globals: true,
10
- },
11
- });