@tymber/common 0.0.1-alpha.0 → 0.1.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.
Files changed (129) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +46 -0
  3. package/dist/App.d.ts +17 -0
  4. package/dist/App.js +236 -0
  5. package/dist/Component.d.ts +16 -0
  6. package/dist/Component.js +116 -0
  7. package/dist/ConfigService.d.ts +31 -0
  8. package/dist/ConfigService.js +75 -0
  9. package/dist/Context.d.ts +41 -0
  10. package/dist/Context.js +10 -0
  11. package/dist/DB.d.ts +15 -0
  12. package/dist/DB.js +7 -0
  13. package/dist/Endpoint.d.ts +21 -0
  14. package/dist/Endpoint.js +87 -0
  15. package/dist/EventEmitter.d.ts +9 -0
  16. package/dist/EventEmitter.js +21 -0
  17. package/dist/Handler.d.ts +8 -0
  18. package/dist/Handler.js +8 -0
  19. package/dist/HttpContext.d.ts +26 -0
  20. package/dist/HttpContext.js +11 -0
  21. package/dist/I18nService.d.ts +18 -0
  22. package/dist/I18nService.js +72 -0
  23. package/dist/Middleware.d.ts +6 -0
  24. package/dist/Middleware.js +4 -0
  25. package/dist/Module.d.ts +47 -0
  26. package/dist/Module.js +12 -0
  27. package/dist/PubSubService.d.ts +20 -0
  28. package/dist/PubSubService.js +60 -0
  29. package/dist/Repository.d.ts +29 -0
  30. package/dist/Repository.js +110 -0
  31. package/dist/Router.d.ts +10 -0
  32. package/dist/Router.js +53 -0
  33. package/dist/TemplateService.d.ts +22 -0
  34. package/dist/TemplateService.js +66 -0
  35. package/dist/View.d.ts +17 -0
  36. package/dist/View.js +48 -0
  37. package/dist/ViewRenderer.d.ts +16 -0
  38. package/dist/ViewRenderer.js +59 -0
  39. package/dist/contrib/accept-language-parser.d.ts +9 -0
  40. package/dist/contrib/accept-language-parser.js +73 -0
  41. package/dist/contrib/cookie.d.ts +33 -0
  42. package/dist/contrib/cookie.js +207 -0
  43. package/dist/contrib/template.d.ts +1 -0
  44. package/dist/contrib/template.js +107 -0
  45. package/dist/index.d.ts +32 -0
  46. package/dist/index.js +32 -0
  47. package/dist/utils/ajv.d.ts +2 -0
  48. package/dist/utils/ajv.js +10 -0
  49. package/dist/utils/camelToSnakeCase.d.ts +1 -0
  50. package/dist/utils/camelToSnakeCase.js +3 -0
  51. package/dist/utils/computeBaseUrl.d.ts +1 -0
  52. package/dist/utils/computeBaseUrl.js +37 -0
  53. package/dist/utils/computeContentType.d.ts +1 -0
  54. package/dist/utils/computeContentType.js +17 -0
  55. package/dist/utils/createDebug.d.ts +1 -0
  56. package/dist/utils/createDebug.js +4 -0
  57. package/dist/utils/createTestApp.d.ts +8 -0
  58. package/dist/utils/createTestApp.js +57 -0
  59. package/dist/utils/escapeValue.d.ts +1 -0
  60. package/dist/utils/escapeValue.js +3 -0
  61. package/dist/utils/fs.d.ts +6 -0
  62. package/dist/utils/fs.js +13 -0
  63. package/dist/utils/isAdmin.d.ts +2 -0
  64. package/dist/utils/isAdmin.js +4 -0
  65. package/dist/utils/isProduction.d.ts +1 -0
  66. package/dist/utils/isProduction.js +1 -0
  67. package/dist/utils/loadModules.d.ts +3 -0
  68. package/dist/utils/loadModules.js +83 -0
  69. package/dist/utils/randomId.d.ts +1 -0
  70. package/dist/utils/randomId.js +4 -0
  71. package/dist/utils/randomUUID.d.ts +1 -0
  72. package/dist/utils/randomUUID.js +4 -0
  73. package/dist/utils/runMigrations.d.ts +3 -0
  74. package/dist/utils/runMigrations.js +85 -0
  75. package/dist/utils/snakeToCamelCase.d.ts +1 -0
  76. package/dist/utils/snakeToCamelCase.js +3 -0
  77. package/dist/utils/sortBy.d.ts +1 -0
  78. package/dist/utils/sortBy.js +8 -0
  79. package/dist/utils/sql.d.ts +120 -0
  80. package/dist/utils/sql.js +433 -0
  81. package/dist/utils/toNodeHandler.d.ts +2 -0
  82. package/dist/utils/toNodeHandler.js +91 -0
  83. package/dist/utils/types.d.ts +3 -0
  84. package/dist/utils/types.js +1 -0
  85. package/dist/utils/waitFor.d.ts +1 -0
  86. package/dist/utils/waitFor.js +5 -0
  87. package/package.json +33 -2
  88. package/src/App.ts +319 -0
  89. package/src/Component.ts +166 -0
  90. package/src/ConfigService.ts +121 -0
  91. package/src/Context.ts +60 -0
  92. package/src/DB.ts +28 -0
  93. package/src/Endpoint.ts +118 -0
  94. package/src/EventEmitter.ts +32 -0
  95. package/src/Handler.ts +14 -0
  96. package/src/HttpContext.ts +35 -0
  97. package/src/I18nService.ts +96 -0
  98. package/src/Middleware.ts +10 -0
  99. package/src/Module.ts +60 -0
  100. package/src/PubSubService.ts +77 -0
  101. package/src/Repository.ts +158 -0
  102. package/src/Router.ts +77 -0
  103. package/src/TemplateService.ts +97 -0
  104. package/src/View.ts +60 -0
  105. package/src/ViewRenderer.ts +71 -0
  106. package/src/contrib/accept-language-parser.ts +94 -0
  107. package/src/contrib/cookie.ts +256 -0
  108. package/src/contrib/template.ts +134 -0
  109. package/src/index.ts +54 -0
  110. package/src/utils/ajv.ts +13 -0
  111. package/src/utils/camelToSnakeCase.ts +3 -0
  112. package/src/utils/computeBaseUrl.ts +46 -0
  113. package/src/utils/computeContentType.ts +17 -0
  114. package/src/utils/createDebug.ts +5 -0
  115. package/src/utils/createTestApp.ts +84 -0
  116. package/src/utils/escapeValue.ts +3 -0
  117. package/src/utils/fs.ts +15 -0
  118. package/src/utils/isAdmin.ts +5 -0
  119. package/src/utils/isProduction.ts +2 -0
  120. package/src/utils/loadModules.ts +105 -0
  121. package/src/utils/randomId.ts +5 -0
  122. package/src/utils/randomUUID.ts +5 -0
  123. package/src/utils/runMigrations.ts +122 -0
  124. package/src/utils/snakeToCamelCase.ts +3 -0
  125. package/src/utils/sortBy.ts +8 -0
  126. package/src/utils/sql.ts +553 -0
  127. package/src/utils/toNodeHandler.ts +121 -0
  128. package/src/utils/types.ts +1 -0
  129. package/src/utils/waitFor.ts +5 -0
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ export { Component, ComponentFactory, INJECT } from "./Component.js";
2
+ export { emptyContext, } from "./Context.js";
3
+ export { ConfigService } from "./ConfigService.js";
4
+ export { DB, DuplicateKeyError } from "./DB.js";
5
+ export { EventEmitter } from "./EventEmitter.js";
6
+ export { PubSubService, NodeClusterPubSubService, initPrimary, } from "./PubSubService.js";
7
+ export { I18nService } from "./I18nService.js";
8
+ export { Endpoint, AdminEndpoint } from "./Endpoint.js";
9
+ export {} from "./HttpContext.js";
10
+ export { App } from "./App.js";
11
+ export { ModuleDefinitions, } from "./Module.js";
12
+ export { View, AdminView } from "./View.js";
13
+ export { Middleware } from "./Middleware.js";
14
+ export { Repository, EntityNotFoundError } from "./Repository.js";
15
+ export { TemplateService } from "./TemplateService.js";
16
+ export { createCookie, parseCookieHeader } from "./contrib/cookie.js";
17
+ export { AJV_INSTANCE } from "./utils/ajv.js";
18
+ export { camelToSnakeCase } from "./utils/camelToSnakeCase.js";
19
+ export { createDebug } from "./utils/createDebug.js";
20
+ export { createTestApp } from "./utils/createTestApp.js";
21
+ export { escapeValue } from "./utils/escapeValue.js";
22
+ export { FS } from "./utils/fs.js";
23
+ export { isAdmin } from "./utils/isAdmin.js";
24
+ export { isProduction } from "./utils/isProduction.js";
25
+ export { randomId } from "./utils/randomId.js";
26
+ export { randomUUID } from "./utils/randomUUID.js";
27
+ export { snakeToCamelCase } from "./utils/snakeToCamelCase.js";
28
+ export { sortBy } from "./utils/sortBy.js";
29
+ export { sql, Statement } from "./utils/sql.js";
30
+ export { toNodeHandler } from "./utils/toNodeHandler.js";
31
+ export {} from "./utils/types.js";
32
+ export { waitFor } from "./utils/waitFor.js";
@@ -0,0 +1,2 @@
1
+ import { Ajv } from "ajv";
2
+ export declare const AJV_INSTANCE: Ajv;
@@ -0,0 +1,10 @@
1
+ import { Ajv } from "ajv";
2
+ import addFormats from "ajv-formats";
3
+ // reference: https://ajv.js.org/api.html
4
+ export const AJV_INSTANCE = new Ajv({
5
+ useDefaults: true,
6
+ coerceTypes: true,
7
+ removeAdditional: true,
8
+ });
9
+ // @ts-expect-error FIXME
10
+ addFormats(AJV_INSTANCE);
@@ -0,0 +1 @@
1
+ export declare function camelToSnakeCase(str: string): string;
@@ -0,0 +1,3 @@
1
+ export function camelToSnakeCase(str) {
2
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
3
+ }
@@ -0,0 +1 @@
1
+ export declare function computeBaseUrl(headers: Headers): string;
@@ -0,0 +1,37 @@
1
+ function removeQuotes(str) {
2
+ if (str.startsWith('"') && str.endsWith('"')) {
3
+ return str.slice(1, -1);
4
+ }
5
+ else {
6
+ return str;
7
+ }
8
+ }
9
+ function parseForwardedHeader(header) {
10
+ const parts = header.split(";").map((part) => part.trim());
11
+ const parsed = {};
12
+ for (const part of parts) {
13
+ const [key, value] = part.split("=");
14
+ if (key && value) {
15
+ parsed[key.toLowerCase()] = removeQuotes(value);
16
+ }
17
+ }
18
+ return `${parsed.proto}://${parsed.host}`;
19
+ }
20
+ export function computeBaseUrl(headers) {
21
+ const forwardedHeader = headers.get("forwarded");
22
+ if (forwardedHeader) {
23
+ const baseUrl = parseForwardedHeader(forwardedHeader);
24
+ if (baseUrl) {
25
+ return baseUrl;
26
+ }
27
+ }
28
+ const forwardedProto = headers.get("x-forwarded-proto");
29
+ const forwardedHost = headers.get("x-forwarded-host");
30
+ if (forwardedProto && forwardedHost) {
31
+ const proto = forwardedProto.split(",");
32
+ const host = forwardedHost.split(",");
33
+ return `${proto[0]}://${host[0]}`;
34
+ }
35
+ const host = headers.get("host");
36
+ return `http://${host}`;
37
+ }
@@ -0,0 +1 @@
1
+ export declare function computeContentType(path: string): string;
@@ -0,0 +1,17 @@
1
+ const CONTENT_TYPES = new Map([
2
+ ["html", "text/html"],
3
+ ["css", "text/css"],
4
+ ["js", "application/javascript"],
5
+ ["json", "application/json"],
6
+ ["png", "image/png"],
7
+ ["jpg", "image/jpeg"],
8
+ ["gif", "image/gif"],
9
+ ["svg", "image/svg+xml"],
10
+ ]);
11
+ export function computeContentType(path) {
12
+ const i = path.lastIndexOf(".");
13
+ if (i === -1)
14
+ return "application/octet-stream";
15
+ const extension = path.substring(i + 1);
16
+ return CONTENT_TYPES.get(extension) || "application/octet-stream";
17
+ }
@@ -0,0 +1 @@
1
+ export declare function createDebug(module: string): import("util").DebugLogger;
@@ -0,0 +1,4 @@
1
+ import { debuglog } from "node:util";
2
+ export function createDebug(module) {
3
+ return debuglog(`tymber:${module}`);
4
+ }
@@ -0,0 +1,8 @@
1
+ import { DB } from "../DB.js";
2
+ import { type Module } from "../Module.js";
3
+ export interface BaseTestContext {
4
+ baseUrl: string;
5
+ db: DB;
6
+ close: () => Promise<void>;
7
+ }
8
+ export declare function createTestApp(initDB: () => DB | Promise<DB>, modules: Module[]): Promise<BaseTestContext>;
@@ -0,0 +1,57 @@
1
+ import { DB } from "../DB.js";
2
+ import { createServer, Server } from "node:http";
3
+ import {} from "node:net";
4
+ import {} from "../Module.js";
5
+ import { App } from "../App.js";
6
+ import { toNodeHandler } from "./toNodeHandler.js";
7
+ const CLOSE_DELAY_MS = 200;
8
+ let sharedTestContext;
9
+ let closeTimer;
10
+ export async function createTestApp(initDB, modules) {
11
+ if (!sharedTestContext) {
12
+ const httpServer = createServer();
13
+ const [port, db] = await Promise.all([
14
+ startHttpServer(httpServer),
15
+ initDB(),
16
+ ]);
17
+ const baseUrl = `http://localhost:${port}`;
18
+ sharedTestContext = {
19
+ httpServer,
20
+ baseUrl,
21
+ db,
22
+ };
23
+ }
24
+ const { httpServer, baseUrl, db } = sharedTestContext;
25
+ const app = await App.create({
26
+ components: [db],
27
+ modules,
28
+ });
29
+ httpServer.removeAllListeners("request");
30
+ httpServer.on("request", toNodeHandler(app.fetch.bind(app)));
31
+ return {
32
+ baseUrl,
33
+ db,
34
+ async close() {
35
+ clearTimeout(closeTimer);
36
+ closeTimer = setTimeout(async () => {
37
+ if (sharedTestContext) {
38
+ const { httpServer } = sharedTestContext;
39
+ sharedTestContext = undefined;
40
+ await Promise.all([closeHttpServer(httpServer), app.close()]);
41
+ }
42
+ }, CLOSE_DELAY_MS);
43
+ },
44
+ };
45
+ }
46
+ function startHttpServer(httpServer) {
47
+ return new Promise((resolve) => {
48
+ httpServer.listen(0, () => {
49
+ resolve(httpServer.address().port);
50
+ });
51
+ });
52
+ }
53
+ function closeHttpServer(httpServer) {
54
+ return new Promise((resolve) => {
55
+ httpServer.close(() => resolve());
56
+ });
57
+ }
@@ -0,0 +1 @@
1
+ export declare function escapeValue(value: string): string;
@@ -0,0 +1,3 @@
1
+ export function escapeValue(value) {
2
+ return value.replaceAll(/([~%_])/g, "~$1");
3
+ }
@@ -0,0 +1,6 @@
1
+ export declare const FS: {
2
+ join: (...paths: string[]) => string;
3
+ createReadStream: (path: string) => import("fs").ReadStream;
4
+ readFile: (path: string) => Promise<string>;
5
+ readDirRecursively: (path: string) => Promise<string[]>;
6
+ };
@@ -0,0 +1,13 @@
1
+ import { join as nodeJoin } from "node:path";
2
+ import { readdir, readFile as nodeReadFile } from "node:fs/promises";
3
+ import { createReadStream as nodeCreateReadStream } from "node:fs";
4
+ // group Node.js-specific methods
5
+ export const FS = {
6
+ join: (...paths) => nodeJoin(...paths),
7
+ createReadStream: (path) => nodeCreateReadStream(path),
8
+ readFile: (path) => nodeReadFile(path, "utf8"),
9
+ readDirRecursively: (path) => readdir(path, {
10
+ encoding: "utf8",
11
+ recursive: true,
12
+ }),
13
+ };
@@ -0,0 +1,2 @@
1
+ import { type HttpContext } from "../HttpContext.js";
2
+ export declare function isAdmin(ctx: HttpContext): boolean;
@@ -0,0 +1,4 @@
1
+ import {} from "../HttpContext.js";
2
+ export function isAdmin(ctx) {
3
+ return ctx.admin !== undefined;
4
+ }
@@ -0,0 +1 @@
1
+ export declare const isProduction: boolean;
@@ -0,0 +1 @@
1
+ export const isProduction = process.env.NODE_ENV === "production" || process.env.ENV === "production";
@@ -0,0 +1,3 @@
1
+ import { ComponentFactory } from "../Component.js";
2
+ import type { Module, ModuleDefinition } from "../Module.js";
3
+ export declare function loadModules(componentFactory: ComponentFactory, modules: Module[]): Promise<ModuleDefinition[]>;
@@ -0,0 +1,83 @@
1
+ import { Component, ComponentFactory } from "../Component.js";
2
+ import {} from "../Router.js";
3
+ import { AdminEndpoint, Endpoint } from "../Endpoint.js";
4
+ import { AdminView, View } from "../View.js";
5
+ import { Middleware } from "../Middleware.js";
6
+ import { createDebug } from "./createDebug.js";
7
+ const debug = createDebug("loadModules");
8
+ export async function loadModules(componentFactory, modules) {
9
+ const moduleDefinitions = [];
10
+ for (const module of modules) {
11
+ const moduleDefinition = {
12
+ name: module.name,
13
+ version: module.version,
14
+ assetsDir: module.assetsDir,
15
+ adminSidebarItems: module.adminSidebarItems,
16
+ endpoints: [],
17
+ views: [],
18
+ adminEndpoints: [],
19
+ adminViews: [],
20
+ middlewares: [],
21
+ };
22
+ const appInit = {
23
+ component(ctor) {
24
+ debug("adding component %s", ctor.name);
25
+ componentFactory.register(ctor);
26
+ },
27
+ endpoint: (method, path, ctor) => {
28
+ debug("adding endpoint %s %s", method, path);
29
+ componentFactory.register(ctor, (handler) => {
30
+ moduleDefinition.endpoints.push({
31
+ method,
32
+ path,
33
+ handlerName: ctor.name,
34
+ handler,
35
+ });
36
+ });
37
+ },
38
+ view: (path, ctor) => {
39
+ debug("adding view %s", path);
40
+ componentFactory.register(ctor, (handler) => {
41
+ moduleDefinition.views.push({
42
+ method: "GET",
43
+ path,
44
+ handlerName: ctor.name,
45
+ handler,
46
+ });
47
+ });
48
+ },
49
+ adminEndpoint: (method, path, ctor) => {
50
+ debug("adding admin endpoint %s %s", method, path);
51
+ componentFactory.register(ctor, (handler) => {
52
+ moduleDefinition.adminEndpoints.push({
53
+ method,
54
+ path,
55
+ handlerName: ctor.name,
56
+ handler,
57
+ });
58
+ });
59
+ },
60
+ adminView: (path, ctor) => {
61
+ debug("adding admin view %s", path);
62
+ componentFactory.register(ctor, (handler) => {
63
+ moduleDefinition.adminViews.push({
64
+ method: "GET",
65
+ path,
66
+ handlerName: ctor.name,
67
+ handler,
68
+ });
69
+ });
70
+ },
71
+ middleware: (ctor) => {
72
+ debug("adding middleware %s", ctor.name);
73
+ componentFactory.register(ctor, (instance) => {
74
+ moduleDefinition.middlewares.push(instance);
75
+ });
76
+ },
77
+ };
78
+ debug("loading module %s", module.name);
79
+ module.init(appInit);
80
+ moduleDefinitions.push(moduleDefinition);
81
+ }
82
+ return moduleDefinitions;
83
+ }
@@ -0,0 +1 @@
1
+ export declare function randomId(): string;
@@ -0,0 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
2
+ export function randomId() {
3
+ return randomBytes(8).toString("hex");
4
+ }
@@ -0,0 +1 @@
1
+ export declare function randomUUID(): `${string}-${string}-${string}-${string}-${string}`;
@@ -0,0 +1,4 @@
1
+ import { randomUUID as nodeRandomUUID } from "node:crypto";
2
+ export function randomUUID() {
3
+ return nodeRandomUUID();
4
+ }
@@ -0,0 +1,3 @@
1
+ import type { ModuleDefinition } from "../Module.js";
2
+ import type { DB } from "../DB.js";
3
+ export declare function runMigrations(db: DB, modules: ModuleDefinition[]): Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { sortBy } from "./sortBy.js";
2
+ import { createDebug } from "./createDebug.js";
3
+ import { emptyContext } from "../Context.js";
4
+ import { FS } from "./fs.js";
5
+ import { sql } from "./sql.js";
6
+ const debug = createDebug("runMigrations");
7
+ // example: 0001-create-users-table.sql
8
+ const MIGRATION_REGEX = /^(\d+)-(.*)\.sql$/;
9
+ export async function runMigrations(db, modules) {
10
+ const ctx = emptyContext();
11
+ debug("creating migrations table");
12
+ await db.createMigrationsTable(ctx);
13
+ for (const module of modules) {
14
+ if (!module.assetsDir) {
15
+ continue;
16
+ }
17
+ const migrationFiles = [];
18
+ for (const { filename, absolutePath } of await readMigrationFiles(db, module)) {
19
+ let match = MIGRATION_REGEX.exec(filename);
20
+ if (match) {
21
+ migrationFiles.push({
22
+ module: module.name,
23
+ id: parseInt(match[1], 10),
24
+ name: match[2],
25
+ sql: await FS.readFile(absolutePath),
26
+ });
27
+ }
28
+ }
29
+ sortBy(migrationFiles, "id");
30
+ for (const migration of migrationFiles) {
31
+ await db.startTransaction(ctx, async () => {
32
+ const result = await db.query(ctx, sql.select().from("t_migrations").where({
33
+ id: migration.id,
34
+ module: migration.module,
35
+ }));
36
+ if (result.length === 1) {
37
+ debug("migration #%d of module %s already applied", migration.id, migration.module);
38
+ return;
39
+ }
40
+ debug("applying migration #%d of module %s", migration.id, migration.module);
41
+ await db.exec(ctx, sql.rawStatement(migration.sql));
42
+ await db.run(ctx, sql
43
+ .insert()
44
+ .into("t_migrations")
45
+ .values([
46
+ {
47
+ module: migration.module,
48
+ id: migration.id,
49
+ name: migration.name,
50
+ run_at: new Date(),
51
+ },
52
+ ]));
53
+ });
54
+ }
55
+ }
56
+ }
57
+ async function readMigrationFiles(db, module) {
58
+ const assetsDir = module.assetsDir;
59
+ if (!assetsDir) {
60
+ return [];
61
+ }
62
+ try {
63
+ const dbSpecificPath = FS.join(assetsDir, "migrations", db.name);
64
+ debug("reading files from %s", dbSpecificPath);
65
+ const filenames = await FS.readDirRecursively(dbSpecificPath);
66
+ return filenames.map((filename) => ({
67
+ filename,
68
+ absolutePath: FS.join(assetsDir, "migrations", db.name, filename),
69
+ }));
70
+ }
71
+ catch (e) {
72
+ try {
73
+ const commonPath = FS.join(assetsDir, "migrations");
74
+ debug("reading files from %s", commonPath);
75
+ const filenames = await FS.readDirRecursively(commonPath);
76
+ return filenames.map((filename) => ({
77
+ filename,
78
+ absolutePath: FS.join(assetsDir, "migrations", filename),
79
+ }));
80
+ }
81
+ catch (e) {
82
+ return [];
83
+ }
84
+ }
85
+ }
@@ -0,0 +1 @@
1
+ export declare function snakeToCamelCase(str: string): string;
@@ -0,0 +1,3 @@
1
+ export function snakeToCamelCase(str) {
2
+ return str.replace(/_([a-z])/g, (letter) => letter[1].toUpperCase());
3
+ }
@@ -0,0 +1 @@
1
+ export declare function sortBy<T>(array: T[], field: keyof T, field2?: keyof T): T[];
@@ -0,0 +1,8 @@
1
+ export function sortBy(array, field, field2) {
2
+ return array.sort((a, b) => {
3
+ if (a[field] === b[field] && field2) {
4
+ return a[field2] < b[field2] ? -1 : 1;
5
+ }
6
+ return a[field] < b[field] ? -1 : 1;
7
+ });
8
+ }
@@ -0,0 +1,120 @@
1
+ export declare function sql(): void;
2
+ export declare namespace sql {
3
+ export var setOption: <T extends keyof Options>(o: T, value: Options[T]) => void;
4
+ export var select: (columns?: Array<string | Expression>) => SelectStatement;
5
+ export var insert: () => InsertStatement;
6
+ export var update: (table: string) => UpdateStatement;
7
+ export var deleteFrom: (table: string) => DeleteStatement;
8
+ export var and: (clauses: Expression[]) => (ctx: BuildContext) => string;
9
+ export var or: (clauses: Expression[]) => (ctx: BuildContext) => string;
10
+ export var not: (expr: Expression) => (ctx: BuildContext) => string;
11
+ export var isNull: (column: string) => () => string;
12
+ export var isNotNull: (column: string) => () => string;
13
+ export var eq: (column: string, value: any) => (ctx: BuildContext) => string;
14
+ export var notEq: (column: string, value: any) => (ctx: BuildContext) => string;
15
+ export var lt: (column: string, value: any) => (ctx: BuildContext) => string;
16
+ export var lte: (column: string, value: any) => (ctx: BuildContext) => string;
17
+ export var gt: (column: string, value: any) => (ctx: BuildContext) => string;
18
+ export var gte: (column: string, value: any) => (ctx: BuildContext) => string;
19
+ export var between: (column: string, low: any, high: any) => (ctx: BuildContext) => string;
20
+ export var like: (column: string, value: any, escapeChar?: string) => (ctx: BuildContext) => string;
21
+ export var ilike: (column: string, value: any, escapeChar?: string) => (ctx: BuildContext) => string;
22
+ var _a: (column: string, values: any[]) => (ctx: BuildContext) => string;
23
+ export var raw: (text: string, values?: any[]) => (ctx: BuildContext) => string;
24
+ export var rawStatement: (text: string) => RawStatement;
25
+ export { _a as in };
26
+ }
27
+ interface Options {
28
+ placeholder: string;
29
+ quoteChar: string;
30
+ }
31
+ interface BuildContext {
32
+ values: any[];
33
+ }
34
+ export declare abstract class Statement {
35
+ build(): {
36
+ text: string;
37
+ values: any[];
38
+ };
39
+ protected abstract computeParts(ctx: BuildContext): Array<string | undefined>;
40
+ }
41
+ export type Expression = (ctx: BuildContext) => string;
42
+ declare class SelectStatement extends Statement {
43
+ private _table?;
44
+ private _distinct;
45
+ private _columns;
46
+ private _joins;
47
+ private _where;
48
+ private _orderBy;
49
+ private _groupBy;
50
+ private _having?;
51
+ private _limit?;
52
+ private _offset?;
53
+ private _forUpdate;
54
+ constructor(columns?: Array<string | Expression>);
55
+ distinct(): this;
56
+ from(table: string): this;
57
+ innerJoin(table: string, on: Record<string, any>): this;
58
+ leftJoin(table: string, on: Record<string, any>): this;
59
+ rightJoin(table: string, on: Record<string, any>): this;
60
+ fullOuterJoin(table: string, on: Record<string, any>): this;
61
+ where(arg: Record<string, any> | Expression): this;
62
+ groupBy(columns: string[]): this;
63
+ having(expr: Expression): this;
64
+ orderBy(columns: string[]): this;
65
+ limit(limit: number): this;
66
+ offset(offset: number): this;
67
+ forUpdate(): this;
68
+ protected computeParts(ctx: BuildContext): (string | undefined)[];
69
+ protected distinctPart(): "DISTINCT" | undefined;
70
+ protected columnsPart(ctx: BuildContext): string;
71
+ protected fromPart(): string | undefined;
72
+ protected joinsPart(ctx: BuildContext): string | undefined;
73
+ protected wherePart(ctx: BuildContext): string | undefined;
74
+ protected groupByPart(): string | undefined;
75
+ protected havingPart(ctx: BuildContext): string | undefined;
76
+ protected orderByPart(): string | undefined;
77
+ protected limitPart(ctx: BuildContext): string | undefined;
78
+ protected offsetPart(ctx: BuildContext): string | undefined;
79
+ protected forUpdatePart(): "FOR UPDATE" | undefined;
80
+ }
81
+ declare class InsertStatement extends Statement {
82
+ private _table?;
83
+ private _values;
84
+ private _select?;
85
+ private _returning;
86
+ into(table: string): this;
87
+ values(values: Array<Record<string, any>>): this;
88
+ select(statement: SelectStatement): this;
89
+ returning(columns?: string[]): this;
90
+ protected computeParts(ctx: BuildContext): (string | undefined)[];
91
+ protected intoPart(): string | undefined;
92
+ protected columnsPart(): string | undefined;
93
+ protected valuesPart(ctx: BuildContext): string | undefined;
94
+ protected returningPart(): string | undefined;
95
+ }
96
+ declare class UpdateStatement extends Statement {
97
+ private readonly table;
98
+ private _values;
99
+ private _where;
100
+ constructor(table: string);
101
+ set(values: Record<string, any>): this;
102
+ where(arg: Record<string, any> | Expression): this;
103
+ protected computeParts(ctx: BuildContext): (string | undefined)[];
104
+ protected setPart(ctx: BuildContext): string;
105
+ protected wherePart(ctx: BuildContext): string | undefined;
106
+ }
107
+ declare class DeleteStatement extends Statement {
108
+ private readonly _table;
109
+ private _where;
110
+ constructor(table: string);
111
+ where(arg: Record<string, any> | Expression): this;
112
+ protected computeParts(ctx: BuildContext): (string | undefined)[];
113
+ protected wherePart(ctx: BuildContext): string | undefined;
114
+ }
115
+ declare class RawStatement extends Statement {
116
+ private readonly text;
117
+ constructor(text: string);
118
+ computeParts(): Array<string | undefined>;
119
+ }
120
+ export {};