@lensjs/core 1.0.1

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 (84) hide show
  1. package/README.md +1 -0
  2. package/copy-front-build.cjs +16 -0
  3. package/package.json +40 -0
  4. package/src/abstracts/adapter.ts +41 -0
  5. package/src/abstracts/store.ts +36 -0
  6. package/src/context/container.ts +67 -0
  7. package/src/context/context.ts +9 -0
  8. package/src/core/api_controller.ts +116 -0
  9. package/src/core/lens.ts +147 -0
  10. package/src/core/watcher.ts +6 -0
  11. package/src/index.ts +11 -0
  12. package/src/stores/better_sqlite.ts +176 -0
  13. package/src/stores/index.ts +1 -0
  14. package/src/types/index.ts +103 -0
  15. package/src/ui/README.md +69 -0
  16. package/src/ui/bun.lock +750 -0
  17. package/src/ui/eslint.config.js +27 -0
  18. package/src/ui/index.html +13 -0
  19. package/src/ui/package-lock.json +2953 -0
  20. package/src/ui/package.json +40 -0
  21. package/src/ui/public/favicon.ico +0 -0
  22. package/src/ui/src/App.tsx +40 -0
  23. package/src/ui/src/components/DetailPanel.tsx +70 -0
  24. package/src/ui/src/components/JsonViewer.tsx +232 -0
  25. package/src/ui/src/components/LoadMore.tsx +25 -0
  26. package/src/ui/src/components/MethodBadge.tsx +19 -0
  27. package/src/ui/src/components/Modal.tsx +48 -0
  28. package/src/ui/src/components/StatusCode.tsx +20 -0
  29. package/src/ui/src/components/Table.tsx +127 -0
  30. package/src/ui/src/components/layout/DeleteButton.tsx +60 -0
  31. package/src/ui/src/components/layout/Footer.tsx +12 -0
  32. package/src/ui/src/components/layout/Header.tsx +40 -0
  33. package/src/ui/src/components/layout/Layout.tsx +49 -0
  34. package/src/ui/src/components/layout/LoadingScreen.tsx +14 -0
  35. package/src/ui/src/components/layout/Sidebar.tsx +67 -0
  36. package/src/ui/src/components/queryFormatters/MongoViewer.tsx +92 -0
  37. package/src/ui/src/components/queryFormatters/QueryViewer.tsx +18 -0
  38. package/src/ui/src/components/queryFormatters/SqlViewer.tsx +105 -0
  39. package/src/ui/src/components/table/NoData.tsx +26 -0
  40. package/src/ui/src/components/tabs/TabbedDataViewer.tsx +77 -0
  41. package/src/ui/src/containers/queries/QueriesContainer.tsx +21 -0
  42. package/src/ui/src/containers/queries/QueryDetailsContainer.tsx +15 -0
  43. package/src/ui/src/containers/requests/RequestDetailsContainer.tsx +16 -0
  44. package/src/ui/src/containers/requests/RequestsContainer.tsx +22 -0
  45. package/src/ui/src/hooks/useLensApi.ts +92 -0
  46. package/src/ui/src/hooks/useLoadMore.ts +48 -0
  47. package/src/ui/src/hooks/useQueries.ts +58 -0
  48. package/src/ui/src/hooks/useRequests.ts +79 -0
  49. package/src/ui/src/hooks/useTanstackApi.ts +126 -0
  50. package/src/ui/src/index.css +78 -0
  51. package/src/ui/src/interfaces/index.ts +10 -0
  52. package/src/ui/src/main.tsx +33 -0
  53. package/src/ui/src/router/Router.ts +11 -0
  54. package/src/ui/src/router/routes/Loading.tsx +5 -0
  55. package/src/ui/src/router/routes/index.tsx +85 -0
  56. package/src/ui/src/types/index.ts +95 -0
  57. package/src/ui/src/utils/api.ts +7 -0
  58. package/src/ui/src/utils/context.ts +24 -0
  59. package/src/ui/src/utils/date.ts +36 -0
  60. package/src/ui/src/views/queries/QueryDetails.tsx +58 -0
  61. package/src/ui/src/views/queries/QueryTable.tsx +21 -0
  62. package/src/ui/src/views/queries/columns.tsx +83 -0
  63. package/src/ui/src/views/requests/BasicRequestDetails.tsx +82 -0
  64. package/src/ui/src/views/requests/RequestDetails.tsx +70 -0
  65. package/src/ui/src/views/requests/RequetsTable.tsx +19 -0
  66. package/src/ui/src/views/requests/columns.tsx +62 -0
  67. package/src/ui/src/vite-env.d.ts +1 -0
  68. package/src/ui/tsconfig.app.json +27 -0
  69. package/src/ui/tsconfig.json +7 -0
  70. package/src/ui/tsconfig.node.json +25 -0
  71. package/src/ui/vite.config.ts +9 -0
  72. package/src/utils/event_emitter.ts +13 -0
  73. package/src/utils/index.ts +176 -0
  74. package/src/watchers/index.ts +2 -0
  75. package/src/watchers/query_watcher.ts +15 -0
  76. package/src/watchers/request_watcher.ts +27 -0
  77. package/tests/core/lens.test.ts +89 -0
  78. package/tests/stores/better_sqlite.test.ts +168 -0
  79. package/tests/utils/index.test.ts +182 -0
  80. package/tests/watchers/query_watcher.test.ts +35 -0
  81. package/tests/watchers/request_watcher.test.ts +59 -0
  82. package/tsconfig.json +3 -0
  83. package/tsup.config.ts +15 -0
  84. package/vitest.config.ts +9 -0
package/README.md ADDED
@@ -0,0 +1 @@
1
+ // README.md
@@ -0,0 +1,16 @@
1
+ const { execSync } = require("child_process");
2
+ const { platform } = require("os");
3
+
4
+ try {
5
+ if (platform() === "win32") {
6
+ console.log("Copying files for Windows...");
7
+ execSync('xcopy /E /I /Y "src/ui/dist" "dist/ui\\"', { stdio: "inherit" });
8
+ } else {
9
+ console.log("Copying files for Unix/Linux...");
10
+ execSync("cp -r src/ui/dist/ dist/ui --force", { stdio: "inherit" });
11
+ }
12
+ console.log("Files copied successfully!");
13
+ } catch (error) {
14
+ console.error("Error copying files:", error);
15
+ process.exit(1);
16
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@lensjs/core",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "private": false,
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/index.cjs",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "build": "tsup --clean && npm run build:ui",
19
+ "build:ui": "npm --prefix src/ui run build",
20
+ "clean": "rm -rf dist",
21
+ "postbuild": "node copy-front-build.cjs",
22
+ "format": "prettier --write .",
23
+ "test": "vitest"
24
+ },
25
+ "devDependencies": {
26
+ "@tailwindcss/vite": "^4.1.11",
27
+ "@types/luxon": "^3.7.1",
28
+ "cross-env": "^10.0.0",
29
+ "luxon": "^3.7.1",
30
+ "prettier": "^3.0.0",
31
+ "vitest": "^3.2.4"
32
+ },
33
+ "dependencies": {
34
+ "emittery": "^1.2.0",
35
+ "libsql": "^0.5.17",
36
+ "sql-formatter": "^15.6.6",
37
+ "tailwind-merge": "^3.3.1"
38
+ },
39
+ "gitHead": "a229fde5700970ec290ff31f1e3b7d84cc56d325"
40
+ }
@@ -0,0 +1,41 @@
1
+ import type Watcher from "../core/watcher";
2
+ import type { RouteDefinition } from "../types";
3
+ import { shouldIgnoreCurrentPath } from "../utils";
4
+
5
+ export default abstract class Adapter {
6
+ private watchers: Watcher[] = [];
7
+ protected ignoredPaths: RegExp[] = [];
8
+ protected onlyPaths: RegExp[] = [];
9
+
10
+ abstract setup(): void;
11
+
12
+ setWatchers(watchers: Watcher[]) {
13
+ this.watchers = watchers;
14
+ return this;
15
+ }
16
+
17
+ setIgnoredPaths(paths: RegExp[]) {
18
+ this.ignoredPaths = paths;
19
+ return this;
20
+ }
21
+
22
+ setOnlyPaths(paths: RegExp[]) {
23
+ this.onlyPaths = paths;
24
+ return this;
25
+ }
26
+
27
+ getWatchers() {
28
+ return this.watchers;
29
+ }
30
+
31
+ shouldIgnorePath(path: string) {
32
+ return shouldIgnoreCurrentPath(path, this.ignoredPaths, this.onlyPaths);
33
+ }
34
+
35
+ abstract registerRoutes(routes: RouteDefinition[]): void;
36
+ abstract serveUI(
37
+ uiPath: string,
38
+ spaRoute: string,
39
+ dataToInject: Record<string, any>,
40
+ ): void;
41
+ }
@@ -0,0 +1,36 @@
1
+ import type {
2
+ PaginationParams,
3
+ Paginator,
4
+ LensEntry,
5
+ WatcherTypeEnum,
6
+ } from "../types";
7
+
8
+ export default abstract class Store {
9
+ abstract initialize(): Promise<void>;
10
+ abstract save(entry: {
11
+ id?: string;
12
+ data: Record<string, any>;
13
+ minimal_data?: Record<string, any>;
14
+ type: WatcherTypeEnum;
15
+ timestamp?: string;
16
+ requestId?: string;
17
+ }): Promise<void>;
18
+ abstract getAllRequests(
19
+ paginationParams: PaginationParams,
20
+ ): Promise<Paginator<Omit<LensEntry, "data">[]>>;
21
+ abstract getAllQueries(
22
+ paginationParams: PaginationParams,
23
+ ): Promise<Paginator<LensEntry[]>>;
24
+ abstract allByRequestId(
25
+ requestId: string,
26
+ type: WatcherTypeEnum,
27
+ ): Promise<LensEntry[]>;
28
+ abstract find(type: WatcherTypeEnum, id: string): Promise<LensEntry|null>;
29
+ abstract truncate(): Promise<void>;
30
+ abstract paginate<T>(
31
+ type: WatcherTypeEnum,
32
+ pagination: PaginationParams,
33
+ ): Promise<Paginator<T>>;
34
+
35
+ abstract count(type: WatcherTypeEnum): Promise<number>;
36
+ }
@@ -0,0 +1,67 @@
1
+ import type Store from "../abstracts/store";
2
+
3
+ type Factory<T = any> = () => T;
4
+
5
+ type ContextStore = {
6
+ store: Store;
7
+ uiConfig: {
8
+ appName: string;
9
+ path: string;
10
+ api: {
11
+ requests: string;
12
+ queries: string;
13
+ };
14
+ };
15
+ };
16
+
17
+ export default class Container {
18
+ private static bindings = new Map<keyof ContextStore, Factory>();
19
+ private static singletons = new Map<keyof ContextStore, Factory>();
20
+ private static instances = new Map<keyof ContextStore, any>();
21
+
22
+ static bind<K extends keyof ContextStore>(
23
+ key: K,
24
+ factory: Factory<ContextStore[K]>,
25
+ ) {
26
+ this.bindings.set(key, factory);
27
+ }
28
+
29
+ static singleton<K extends keyof ContextStore>(
30
+ key: K,
31
+ factory: Factory<ContextStore[K]>,
32
+ ) {
33
+ this.singletons.set(key, factory);
34
+ }
35
+
36
+ static make<K extends keyof ContextStore>(key: K): ContextStore[K] {
37
+ if (this.instances.has(key)) {
38
+ return this.instances.get(key);
39
+ }
40
+
41
+ if (this.singletons.has(key)) {
42
+ const instance = this.singletons.get(key)!();
43
+ this.instances.set(key, instance);
44
+ return instance;
45
+ }
46
+
47
+ if (this.bindings.has(key)) {
48
+ return this.bindings.get(key)!();
49
+ }
50
+
51
+ throw new Error(`Service "${key}" is not bound in the container`);
52
+ }
53
+
54
+ static has(key: keyof ContextStore): boolean {
55
+ return (
56
+ this.bindings.has(key) ||
57
+ this.singletons.has(key) ||
58
+ this.instances.has(key)
59
+ );
60
+ }
61
+
62
+ static clear(): void {
63
+ this.bindings.clear();
64
+ this.singletons.clear();
65
+ this.instances.clear();
66
+ }
67
+ }
@@ -0,0 +1,9 @@
1
+ import Container from "./container";
2
+
3
+ export const getStore = () => {
4
+ return Container.make("store");
5
+ };
6
+
7
+ export const getUiConfig = () => {
8
+ return Container.make("uiConfig");
9
+ };
@@ -0,0 +1,116 @@
1
+ import { getStore, getUiConfig } from "../context/context";
2
+ import { WatcherTypeEnum } from "../types";
3
+ import type {
4
+ ApiResponse,
5
+ LensEntry as LensEntry,
6
+ Paginator,
7
+ RouteDefinitionHandler,
8
+ } from "../types";
9
+
10
+ export class ApiController {
11
+ static async getRequests({ qs }: RouteDefinitionHandler) {
12
+ return this.paginatedResponse(
13
+ await getStore().getAllRequests(this.extractPaginationParams(qs)),
14
+ );
15
+ }
16
+
17
+ static async getRequest({
18
+ params,
19
+ }: RouteDefinitionHandler): Promise<ApiResponse<LensEntry>> {
20
+ const request = await getStore().find(WatcherTypeEnum.REQUEST, params.id);
21
+
22
+ if (!request) {
23
+ return this.notFoundResponse();
24
+ }
25
+
26
+ return this.resourceResponse(request);
27
+ }
28
+
29
+ static async getQueries({
30
+ qs,
31
+ }: RouteDefinitionHandler): Promise<ApiResponse<LensEntry[]>> {
32
+ const queries = await getStore().getAllQueries(
33
+ this.extractPaginationParams(qs),
34
+ );
35
+
36
+ return this.paginatedResponse(queries);
37
+ }
38
+
39
+ static async getQuery({
40
+ params,
41
+ }: RouteDefinitionHandler): Promise<ApiResponse<LensEntry>> {
42
+ const query = await getStore().find(WatcherTypeEnum.QUERY, params.id);
43
+
44
+ if (!query) {
45
+ return this.notFoundResponse();
46
+ }
47
+
48
+ return this.resourceResponse(query);
49
+ }
50
+
51
+ static async truncate() {
52
+ await getStore().truncate();
53
+
54
+ return this.baseResponse({}, 200, "All entries cleared");
55
+ }
56
+
57
+ static fetchUiConfig() {
58
+ return getUiConfig();
59
+ }
60
+
61
+ private static extractPaginationParams(qs?: Record<string, any>) {
62
+ if (!qs || Object.keys(qs).length === 0) {
63
+ return { page: 1, perPage: 100 };
64
+ }
65
+
66
+ let page = Number(qs.page);
67
+ let perPage = Number(qs.perPage);
68
+
69
+ if (!Number.isInteger(perPage) || perPage > 100 || perPage < 5) {
70
+ perPage = 100;
71
+ }
72
+
73
+ if (!Number.isInteger(page) || page < 1) {
74
+ page = 1;
75
+ }
76
+
77
+ return { page, perPage };
78
+ }
79
+
80
+ private static resourceResponse<T extends Object>(data: T): ApiResponse<T> {
81
+ return this.baseResponse<T>(data, 200, "Data fetched successfully");
82
+ }
83
+
84
+ private static notFoundResponse<T extends Object>(
85
+ message = "Could not find the requested resource",
86
+ ): ApiResponse<T> {
87
+ return this.baseResponse<T>(null, 404, message);
88
+ }
89
+
90
+ private static paginatedResponse<T extends Object>(
91
+ data: Paginator<T>,
92
+ ): ApiResponse<T> {
93
+ return this.baseResponse<T>(data, 200, "Data fetched successfully");
94
+ }
95
+
96
+ private static baseResponse<T extends Object>(
97
+ data: Paginator<T> | T | null,
98
+ status: number,
99
+ message: string,
100
+ ): ApiResponse<T> {
101
+ if (!data) {
102
+ return { status, message, data: null };
103
+ }
104
+
105
+ if ("meta" in data) {
106
+ return {
107
+ status,
108
+ message,
109
+ data: data.data,
110
+ meta: data.meta,
111
+ };
112
+ }
113
+
114
+ return { status, message, data };
115
+ }
116
+ }
@@ -0,0 +1,147 @@
1
+ import type Store from "../abstracts/store";
2
+ import type Adapter from "../abstracts/adapter";
3
+ import Watcher from "./watcher";
4
+ import { ApiController } from "./api_controller";
5
+ import * as path from "node:path";
6
+ import type {
7
+ LensConfig,
8
+ RouteDefinitionHandler,
9
+ WatcherTypeEnum,
10
+ } from "../types/index.ts";
11
+ import { getUiConfig } from "../context/context";
12
+ import Container from "../context/container";
13
+ import { BetterSqliteStore } from "../stores/index";
14
+ import { getMeta } from "../utils/index";
15
+
16
+ export default class Lens {
17
+ private static watchers: Map<WatcherTypeEnum, Watcher> = new Map();
18
+ private static store: Store;
19
+ private static adapter: Adapter;
20
+
21
+ static watch(watcher: Watcher): typeof Lens {
22
+ this.watchers.set(watcher.name, watcher);
23
+ return this;
24
+ }
25
+
26
+ static setWatchers(watchers: Watcher[]): typeof Lens {
27
+ this.watchers = new Map(watchers.map((watcher) => [watcher.name, watcher]));
28
+ return this;
29
+ }
30
+
31
+ static async start(
32
+ config: LensConfig = {
33
+ basePath: "lens",
34
+ appName: "Lens",
35
+ enabled: true,
36
+ },
37
+ ) {
38
+ if (!config.enabled) {
39
+ return;
40
+ }
41
+ await this.bindContainerDeps(config);
42
+
43
+ let adapter = this.getAdapter();
44
+
45
+ adapter.setWatchers(Array.from(this.watchers.values())).setup();
46
+
47
+ const { apiRoutes } = this.getRoutes({
48
+ basePath: config.basePath,
49
+ });
50
+
51
+ adapter.registerRoutes(apiRoutes);
52
+
53
+ const { __dirname } = getMeta(import.meta.url);
54
+ const uiPath = path.resolve(this.normalizeDirName(__dirname), "ui");
55
+ adapter.serveUI(uiPath, config.basePath, getUiConfig());
56
+ }
57
+
58
+ static setStore(store: Store): typeof Lens {
59
+ this.store = store;
60
+ return this;
61
+ }
62
+
63
+ static async getStore(): Promise<Store> {
64
+ return this.store ?? (await this.getDefaultStore());
65
+ }
66
+
67
+ static setAdapter(adapter: Adapter): typeof Lens {
68
+ this.adapter = adapter;
69
+ return this;
70
+ }
71
+
72
+ static getAdapter(): Adapter {
73
+ if (!this.adapter) {
74
+ throw new Error("No adapter has been set");
75
+ }
76
+
77
+ return this.adapter;
78
+ }
79
+
80
+ private static async bindContainerDeps(config: LensConfig) {
81
+ const dbStore = await this.getStore();
82
+ Container.singleton("store", () => dbStore);
83
+ Container.singleton("uiConfig", () => {
84
+ return {
85
+ appName: config.appName,
86
+ path: `/${config.basePath}`,
87
+ api: {
88
+ requests: `/${config.basePath}/api/requests`,
89
+ queries: `/${config.basePath}/api/queries`,
90
+ truncate: `/${config.basePath}/api/truncate`,
91
+ },
92
+ };
93
+ });
94
+ }
95
+
96
+ private static getRoutes({ basePath }: { basePath: string }) {
97
+ const apiRoutes = [
98
+ {
99
+ method: "GET" as const,
100
+ path: `/lens-config`,
101
+ handler: () => ApiController.fetchUiConfig(),
102
+ },
103
+ {
104
+ method: "GET" as const,
105
+ path: `${basePath}/api/requests`,
106
+ handler: async (data: RouteDefinitionHandler) =>
107
+ await ApiController.getRequests(data),
108
+ },
109
+ {
110
+ method: "GET" as const,
111
+ path: `${basePath}/api/requests/:id`,
112
+ handler: async (data: RouteDefinitionHandler) =>
113
+ await ApiController.getRequest(data),
114
+ },
115
+ {
116
+ method: "GET" as const,
117
+ path: `${basePath}/api/queries`,
118
+ handler: async (data: RouteDefinitionHandler) =>
119
+ await ApiController.getQueries(data),
120
+ },
121
+ {
122
+ method: "GET" as const,
123
+ path: `${basePath}/api/queries/:id`,
124
+ handler: async (data: RouteDefinitionHandler) =>
125
+ await ApiController.getQuery(data),
126
+ },
127
+ {
128
+ method: "DELETE" as const,
129
+ path: `${basePath}/api/truncate`,
130
+ handler: async () => await ApiController.truncate(),
131
+ },
132
+ ];
133
+
134
+ return { apiRoutes };
135
+ }
136
+
137
+ private static async getDefaultStore(): Promise<Store> {
138
+ const store = new BetterSqliteStore();
139
+ await store.initialize();
140
+
141
+ return store;
142
+ }
143
+
144
+ private static normalizeDirName(path: string) {
145
+ return path.replace(/(\/packages\/)[^/]+(?=\/dist)/, "$1core");
146
+ }
147
+ }
@@ -0,0 +1,6 @@
1
+ import type { WatcherTypeEnum } from "../types";
2
+
3
+ export default abstract class Watcher {
4
+ abstract name: WatcherTypeEnum;
5
+ abstract log(data: any): Promise<void>;
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { default as Lens } from "./core/lens";
2
+
3
+ export * from "./stores";
4
+ export * from "./watchers";
5
+ export * from "./types/index";
6
+ export { default as LensAdapter } from "./abstracts/adapter";
7
+ export { default as LensStore } from "./abstracts/store";
8
+ export { default as LensWatcher } from "./core/watcher";
9
+ export { getStore as getLensStore } from "./context/context";
10
+ export * as lensUtils from "./utils/index";
11
+ export { lensEmitter, lensResource, createEmittery} from "./utils/event_emitter";
@@ -0,0 +1,176 @@
1
+ import Store from "../abstracts/store";
2
+ import { randomUUID } from "crypto";
3
+ import {
4
+ WatcherTypeEnum,
5
+ type PaginationParams,
6
+ type LensEntry,
7
+ } from "../types/index";
8
+ import Database from "libsql";
9
+ import { sqlDateTime } from "../utils";
10
+
11
+ const TABLE_NAME = "lens_entries";
12
+
13
+ export default class BetterSqliteStore extends Store {
14
+ protected connection!: Database.Database;
15
+
16
+ public async initialize() {
17
+ this.connection = new Database("lens.db");
18
+
19
+ this.setupSchema();
20
+ console.log("Connected to Lens (SQLite) database.");
21
+ }
22
+
23
+ public async truncate() {
24
+ this.connection.prepare(`DELETE FROM ${TABLE_NAME};`).run();
25
+ }
26
+
27
+ public async save(entry: {
28
+ id?: string;
29
+ data: Record<string, any>;
30
+ minimal_data?: Record<string, any>;
31
+ type: WatcherTypeEnum;
32
+ timestamp?: string;
33
+ requestId?: string;
34
+ }) {
35
+ this.connection
36
+ .prepare(
37
+ `INSERT INTO ${TABLE_NAME} (id, data, type, created_at, lens_entry_id, minimal_data) values($id, $data, $type, $created_at, $lens_entry_id, $minimalData)`
38
+ )
39
+ .run({
40
+ id: entry.id ?? randomUUID(),
41
+ data: JSON.stringify(entry.data),
42
+ type: entry.type,
43
+ created_at: entry.timestamp ?? sqlDateTime(),
44
+ lens_entry_id: entry.requestId || null,
45
+ minimalData: JSON.stringify(entry.minimal_data ?? {}),
46
+ });
47
+ }
48
+
49
+ override async getAllQueries<T extends LensEntry[]>(
50
+ pagination: PaginationParams
51
+ ) {
52
+ return await this.paginate<T>(WatcherTypeEnum.QUERY, pagination);
53
+ }
54
+
55
+ override async getAllRequests<T extends Omit<LensEntry, "data">[]>(
56
+ pagination: PaginationParams
57
+ ) {
58
+ return await this.paginate<T>(WatcherTypeEnum.REQUEST, pagination, false);
59
+ }
60
+
61
+ public async allByRequestId(requestId: string, type: WatcherTypeEnum) {
62
+ const rows = this.connection
63
+ .prepare(
64
+ `${this.getSelectedColumns()} FROM ${TABLE_NAME} WHERE type = $type AND lens_entry_id = $requestId ORDER BY created_at DESC`
65
+ )
66
+ .all({ type, requestId });
67
+
68
+ return this.mapRows(rows);
69
+ }
70
+
71
+ public async paginate<T>(
72
+ type: WatcherTypeEnum,
73
+ { page, perPage }: PaginationParams,
74
+ includeFullData: boolean = true
75
+ ): Promise<{
76
+ meta: {
77
+ total: number;
78
+ lastPage: number;
79
+ currentPage: number;
80
+ };
81
+ data: T;
82
+ }> {
83
+ const offset = (page - 1) * perPage;
84
+ const query = `${this.getSelectedColumns(
85
+ includeFullData
86
+ )} FROM ${TABLE_NAME} WHERE type = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`;
87
+ const count = await this.count(type);
88
+ const rows = this.connection.prepare(query).all(type, perPage, offset);
89
+ const mappedRows = this.mapRows(rows, includeFullData);
90
+
91
+ return {
92
+ meta: {
93
+ total: count,
94
+ lastPage: Math.ceil(count / perPage),
95
+ currentPage: page,
96
+ },
97
+ data: mappedRows as T,
98
+ };
99
+ }
100
+
101
+ override async count(type: WatcherTypeEnum): Promise<number> {
102
+ const result = this.connection
103
+ .prepare(`SELECT count(*) as count FROM ${TABLE_NAME} WHERE type = ?`)
104
+ .get(type) as { count: number };
105
+
106
+ return Number(result.count);
107
+ }
108
+
109
+ public async find(type: WatcherTypeEnum, id: string) {
110
+ const row = this.connection
111
+ .prepare(
112
+ `${this.getSelectedColumns()} FROM ${TABLE_NAME} WHERE id = ? AND type = ? LIMIT 1`
113
+ )
114
+ .get(id, type);
115
+
116
+ if (!row) {
117
+ return null;
118
+ }
119
+
120
+ return this.mapRow(row, true);
121
+ }
122
+
123
+ private setupSchema() {
124
+ const createTable = `
125
+ CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
126
+ id TEXT PRIMARY KEY,
127
+ minimal_data TEXT,
128
+ data TEXT NOT NULL,
129
+ type TEXT NOT NULL,
130
+ created_at TEXT NOT NULL,
131
+ updated_at TEXT,
132
+ lens_entry_id TEXT NULL
133
+ );
134
+ `;
135
+
136
+ const createIndex = `
137
+ CREATE INDEX IF NOT EXISTS lens_entries_id_type_index
138
+ ON ${TABLE_NAME} (id, type);
139
+ `;
140
+
141
+ this.connection.exec(createTable);
142
+ this.connection.exec(createIndex);
143
+ }
144
+
145
+ protected mapRow(row: any, includeFullData = true): LensEntry {
146
+ let data = includeFullData ? JSON.parse(row.data) : {};
147
+
148
+ if (!includeFullData) {
149
+ data = JSON.parse(row.minimal_data);
150
+ }
151
+
152
+ return {
153
+ id: row.id,
154
+ type: row.type,
155
+ created_at: row.created_at,
156
+ lens_entry_id: row.lens_entry_id,
157
+ data: data,
158
+ };
159
+ }
160
+
161
+ protected mapRows(rows: any[], includeFullData = true) {
162
+ let mappedRows: LensEntry[] = [];
163
+
164
+ for (const row of rows) {
165
+ mappedRows.push(this.mapRow(row, includeFullData));
166
+ }
167
+
168
+ return mappedRows;
169
+ }
170
+
171
+ protected getSelectedColumns(includeFullData: boolean = true) {
172
+ return `SELECT id, minimal_data, type, created_at, lens_entry_id ${
173
+ includeFullData ? ",data" : ""
174
+ }`;
175
+ }
176
+ }
@@ -0,0 +1 @@
1
+ export { default as BetterSqliteStore } from "./better_sqlite";