web_plsql 1.2.0 → 1.3.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 (119) hide show
  1. package/README.md +103 -8
  2. package/dist/chunk-DQk6qfdC.mjs +18 -0
  3. package/dist/frontend/assets/main-C8GKnEhz.js +147 -0
  4. package/dist/frontend/assets/main-C8GKnEhz.js.br +0 -0
  5. package/dist/frontend/assets/main-C8GKnEhz.js.gz +0 -0
  6. package/dist/frontend/assets/main-bU4x_yEW.css +1 -0
  7. package/dist/frontend/assets/main-bU4x_yEW.css.br +0 -0
  8. package/dist/frontend/assets/main-bU4x_yEW.css.gz +0 -0
  9. package/{src/admin → dist/frontend}/index.html +12 -25
  10. package/dist/frontend/index.html.br +0 -0
  11. package/dist/frontend/index.html.gz +0 -0
  12. package/dist/index.d.mts +646 -0
  13. package/dist/index.mjs +3559 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
  16. package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
  17. package/examples/config-apex.js +1 -1
  18. package/examples/config-docker.js +1 -2
  19. package/package.json +48 -32
  20. package/examples/config-native.js +0 -68
  21. package/src/admin/client/charts.ts +0 -598
  22. package/src/admin/client/main.js +0 -3
  23. package/src/admin/client/tailwind.css +0 -41
  24. package/src/admin/js/api.ts +0 -170
  25. package/src/admin/js/app.ts +0 -614
  26. package/src/admin/js/eslint.config.js +0 -74
  27. package/src/admin/js/schemas.ts +0 -215
  28. package/src/admin/js/templates/config.ts +0 -147
  29. package/src/admin/js/templates/errorRow.ts +0 -21
  30. package/src/admin/js/templates/index.ts +0 -4
  31. package/src/admin/js/templates/poolCard.ts +0 -61
  32. package/src/admin/js/templates/traceRow.ts +0 -24
  33. package/src/admin/js/tsconfig.json +0 -24
  34. package/src/admin/js/types.ts +0 -336
  35. package/src/admin/js/ui/components.ts +0 -44
  36. package/src/admin/js/ui/table.ts +0 -173
  37. package/src/admin/js/ui/theme.ts +0 -93
  38. package/src/admin/js/ui/views.ts +0 -427
  39. package/src/admin/js/util/format.ts +0 -46
  40. package/src/admin/js/util/metrics.ts +0 -24
  41. package/src/admin/lib/chart.bundle.css +0 -1
  42. package/src/admin/lib/chart.bundle.js +0 -146
  43. package/src/admin/style.css +0 -1437
  44. package/src/bin/load-test.js +0 -202
  45. package/src/handler/handlerAdmin.js +0 -293
  46. package/src/handler/handlerLogger.js +0 -21
  47. package/src/handler/handlerUpload.js +0 -26
  48. package/src/handler/plsql/cgi.js +0 -131
  49. package/src/handler/plsql/errorPage.js +0 -113
  50. package/src/handler/plsql/handlerPlSql.js +0 -90
  51. package/src/handler/plsql/owaPageStream.js +0 -93
  52. package/src/handler/plsql/parsePage.js +0 -213
  53. package/src/handler/plsql/procedure.js +0 -426
  54. package/src/handler/plsql/procedureError.js +0 -40
  55. package/src/handler/plsql/procedureNamed.js +0 -282
  56. package/src/handler/plsql/procedureSanitize.js +0 -286
  57. package/src/handler/plsql/procedureVariable.js +0 -56
  58. package/src/handler/plsql/request.js +0 -124
  59. package/src/handler/plsql/requestError.js +0 -23
  60. package/src/handler/plsql/sendResponse.js +0 -174
  61. package/src/handler/plsql/stream.js +0 -28
  62. package/src/handler/plsql/upload.js +0 -109
  63. package/src/index.js +0 -19
  64. package/src/server/adminContext.js +0 -23
  65. package/src/server/config.js +0 -57
  66. package/src/server/connections.js +0 -40
  67. package/src/server/server.js +0 -304
  68. package/src/types.js +0 -150
  69. package/src/util/cache.js +0 -123
  70. package/src/util/errorToString.js +0 -24
  71. package/src/util/file.js +0 -91
  72. package/src/util/html.js +0 -53
  73. package/src/util/jsonLogger.js +0 -47
  74. package/src/util/oracle.js +0 -80
  75. package/src/util/shutdown.js +0 -44
  76. package/src/util/statsManager.js +0 -368
  77. package/src/util/trace.js +0 -326
  78. package/src/util/traceManager.js +0 -68
  79. package/src/util/type.js +0 -6
  80. package/src/util/util.js +0 -66
  81. package/src/version.js +0 -5
  82. package/types/admin/js/schemas.d.ts +0 -384
  83. package/types/admin/js/types.d.ts +0 -312
  84. package/types/handler/handlerAdmin.d.ts +0 -79
  85. package/types/handler/handlerLogger.d.ts +0 -2
  86. package/types/handler/handlerUpload.d.ts +0 -7
  87. package/types/handler/plsql/cgi.d.ts +0 -4
  88. package/types/handler/plsql/errorPage.d.ts +0 -11
  89. package/types/handler/plsql/handlerPlSql.d.ts +0 -13
  90. package/types/handler/plsql/owaPageStream.d.ts +0 -28
  91. package/types/handler/plsql/parsePage.d.ts +0 -3
  92. package/types/handler/plsql/procedure.d.ts +0 -13
  93. package/types/handler/plsql/procedureError.d.ts +0 -23
  94. package/types/handler/plsql/procedureNamed.d.ts +0 -14
  95. package/types/handler/plsql/procedureSanitize.d.ts +0 -11
  96. package/types/handler/plsql/procedureVariable.d.ts +0 -10
  97. package/types/handler/plsql/request.d.ts +0 -9
  98. package/types/handler/plsql/requestError.d.ts +0 -8
  99. package/types/handler/plsql/sendResponse.d.ts +0 -6
  100. package/types/handler/plsql/upload.d.ts +0 -5
  101. package/types/index.d.ts +0 -10
  102. package/types/server/adminContext.d.ts +0 -17
  103. package/types/server/config.d.ts +0 -2
  104. package/types/server/server.d.ts +0 -57
  105. package/types/types.d.ts +0 -328
  106. package/types/util/cache.d.ts +0 -69
  107. package/types/util/errorToString.d.ts +0 -1
  108. package/types/util/file.d.ts +0 -6
  109. package/types/util/html.d.ts +0 -3
  110. package/types/util/jsonLogger.d.ts +0 -45
  111. package/types/util/oracle.d.ts +0 -5
  112. package/types/util/shutdown.d.ts +0 -2
  113. package/types/util/statsManager.d.ts +0 -395
  114. package/types/util/trace.d.ts +0 -22
  115. package/types/util/traceManager.d.ts +0 -35
  116. package/types/util/type.d.ts +0 -1
  117. package/types/util/util.d.ts +0 -3
  118. package/types/version.d.ts +0 -1
  119. /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
@@ -1,304 +0,0 @@
1
- import debugModule from 'debug';
2
- const debug = debugModule('webplsql:server');
3
- import http from 'node:http';
4
- import https from 'node:https';
5
- import path from 'node:path';
6
- import {fileURLToPath} from 'node:url';
7
- import express from 'express';
8
- import cookieParser from 'cookie-parser';
9
- import compression from 'compression';
10
- import {z$configType} from '../types.js';
11
- import {installShutdown} from '../util/shutdown.js';
12
- import {poolCreate, poolsClose} from '../util/oracle.js';
13
- import {handlerUpload} from '../handler/handlerUpload.js';
14
- import {handlerLogger} from '../handler/handlerLogger.js';
15
- import {handlerWebPlSql} from '../handler/plsql/handlerPlSql.js';
16
- import {handlerAdmin} from '../handler/handlerAdmin.js';
17
- import {readFileSyncUtf8, getJsonFile} from '../util/file.js';
18
- import {showConfig} from './config.js';
19
- import {AdminContext} from './adminContext.js';
20
-
21
- const __filename = fileURLToPath(import.meta.url);
22
- const __dirname = path.dirname(__filename);
23
-
24
- /**
25
- * @typedef {import('node:net').Socket} Socket
26
- * @typedef {import('express').Express} Express
27
- * @typedef {import('express').Request} Request
28
- * @typedef {import('express').Response} Response
29
- * @typedef {import('express').NextFunction} NextFunction
30
- * @typedef {import('oracledb').Pool} Pool
31
- * @typedef {import('../types.js').environmentType} environmentType
32
- * @typedef {import('../types.js').configType} configType
33
- * @typedef {import('../handler/plsql/procedureNamed.js').argsType} argsType
34
- */
35
-
36
- /**
37
- * @typedef {import('express').RequestHandler & {
38
- * procedureNameCache: import('../util/cache.js').Cache<string>;
39
- * argumentCache: import('../util/cache.js').Cache<argsType>;
40
- * }} ExtendedRequestHandler
41
- */
42
-
43
- /**
44
- * @typedef {object} webServer - Web server interface.
45
- * @property {configType} config - Configuration object.
46
- * @property {Pool[]} connectionPools - Oracle connection pools.
47
- * @property {Express} app - Express app.
48
- * @property {http.Server | https.Server} server - Native Node http(s) server instance.
49
- * @property {() => Promise<void>} shutdown - Shutdown function.
50
- */
51
-
52
- /**
53
- * @typedef {object} sslConfig - SSL configuration.
54
- * @property {string} keyFilename - key filename.
55
- * @property {string} certFilename - cert filename.
56
- */
57
-
58
- /**
59
- * Create HTTPS server.
60
- * @param {Express} app - express application
61
- * @param {sslConfig} [ssl] - ssl configuration.
62
- * @returns {http.Server | https.Server} - server
63
- */
64
- export const createServer = (app, ssl) => {
65
- if (ssl) {
66
- const key = readFileSyncUtf8(ssl.keyFilename);
67
- const cert = readFileSyncUtf8(ssl.certFilename);
68
-
69
- return https.createServer({key, cert}, app);
70
- } else {
71
- return http.createServer(app);
72
- }
73
- };
74
-
75
- /**
76
- * Start server.
77
- * @param {configType} config - The config.
78
- * @param {sslConfig} [ssl] - ssl configuration.
79
- * @returns {Promise<webServer>} - Promise resolving to the web server object.
80
- */
81
- export const startServer = async (config, ssl) => {
82
- debug('startServer: BEGIN', config, ssl);
83
-
84
- const internalConfig = /** @type {configType} */ (z$configType.parse(config));
85
- AdminContext.config = internalConfig;
86
-
87
- showConfig(internalConfig);
88
-
89
- // Create express app
90
- const app = express();
91
-
92
- // Default middleware
93
- app.use(handlerUpload(internalConfig.uploadFileSizeLimit));
94
- app.use(express.json({limit: '50mb'}));
95
- app.use(express.urlencoded({limit: '50mb', extended: true}));
96
- app.use(cookieParser());
97
- app.use(compression());
98
-
99
- // Pause & Admin Auth middleware
100
- app.use((req, res, next) => {
101
- const adminRoute = internalConfig.adminRoute ?? '/admin';
102
-
103
- // Simple pause check for all PL/SQL routes (not admin)
104
- if (AdminContext.paused && !req.path.startsWith(adminRoute)) {
105
- res.status(503).send('Server Paused');
106
- return;
107
- }
108
-
109
- // Basic Auth for Admin Route
110
- if (req.path.startsWith(adminRoute)) {
111
- const user = internalConfig.adminUser;
112
- const pass = internalConfig.adminPassword;
113
-
114
- if (user && pass) {
115
- const auth = {login: user, password: pass};
116
- const b64auth = (req.headers.authorization ?? '').split(' ')[1] ?? '';
117
- const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':');
118
-
119
- if (login !== auth.login || password !== auth.password) {
120
- res.set('WWW-Authenticate', 'Basic realm="Admin Console"');
121
- res.status(401).send('Authentication required.');
122
- return;
123
- }
124
- }
125
- }
126
-
127
- next();
128
- });
129
-
130
- // Access log
131
- if (internalConfig.loggerFilename.length > 0) {
132
- app.use(handlerLogger(internalConfig.loggerFilename));
133
- }
134
-
135
- // Admin console
136
- const adminRoute = internalConfig.adminRoute ?? '/admin';
137
- const adminDirectory = path.resolve(__dirname, '../admin');
138
-
139
- // Ensure trailing slash for admin route to support relative paths
140
- app.get(adminRoute, (req, res, next) => {
141
- const [path] = req.originalUrl.split('?');
142
- if (path === adminRoute) {
143
- const query = req.originalUrl.split('?')[1];
144
- return res.redirect(adminRoute + '/' + (query ? '?' + query : ''));
145
- }
146
- next();
147
- });
148
-
149
- app.use(adminRoute, handlerAdmin);
150
- app.use(adminRoute, express.static(adminDirectory));
151
-
152
- // Serving static files
153
- for (const i of internalConfig.routeStatic) {
154
- app.use(i.route, express.static(i.directoryPath));
155
- }
156
-
157
- /** @type {Pool[]} */
158
- const connectionPools = [];
159
- AdminContext.pools = connectionPools;
160
- AdminContext.caches = [];
161
-
162
- // Oracle pl/sql express middleware
163
- for (const i of internalConfig.routePlSql) {
164
- // Allocate the Oracle database pool
165
- const pool = await poolCreate(i.user, i.password, i.connectString);
166
- connectionPools.push(pool);
167
-
168
- const handler = handlerWebPlSql(pool, i);
169
-
170
- // Capture caches for admin console
171
- AdminContext.caches.push({
172
- poolName: i.route,
173
- procedureNameCache: handler.procedureNameCache,
174
- argumentCache: handler.argumentCache,
175
- });
176
-
177
- app.use([`${i.route}/:name`, i.route], (req, res, next) => {
178
- const start = process.hrtime();
179
- res.on('finish', () => {
180
- const diff = process.hrtime(start);
181
- const duration = diff[0] * 1000 + diff[1] / 1_000_000;
182
- AdminContext.statsManager.recordRequest(duration, res.statusCode >= 400);
183
- });
184
- handler(req, res, next);
185
- });
186
- }
187
-
188
- // Update pools in StatsManager on each rotation
189
- const originalRotate = AdminContext.statsManager.rotateBucket.bind(AdminContext.statsManager);
190
- AdminContext.statsManager.rotateBucket = () => {
191
- const poolSnapshots = AdminContext.pools.map((pool, index) => {
192
- const cache = AdminContext.caches[index];
193
- const name = cache?.poolName ?? `pool-${index}`;
194
- const procStats = cache?.procedureNameCache.getStats();
195
- const argStats = cache?.argumentCache.getStats();
196
-
197
- return {
198
- name,
199
- connectionsOpen: pool.connectionsOpen,
200
- connectionsInUse: pool.connectionsInUse,
201
- cache: {
202
- procedureName: {
203
- size: cache?.procedureNameCache.keys().length ?? 0,
204
- hits: procStats?.hits ?? 0,
205
- misses: procStats?.misses ?? 0,
206
- },
207
- argument: {
208
- size: cache?.argumentCache.keys().length ?? 0,
209
- hits: argStats?.hits ?? 0,
210
- misses: argStats?.misses ?? 0,
211
- },
212
- },
213
- };
214
- });
215
- originalRotate(poolSnapshots);
216
- };
217
-
218
- // create server
219
- debug('startServer: createServer');
220
- const server = createServer(app, ssl);
221
-
222
- // Track open connections
223
- /** @type {Set<Socket>} */
224
- const connections = new Set();
225
- server.on('connection', (/** @type {Socket} */ socket) => {
226
- connections.add(socket);
227
- socket.on('close', () => {
228
- connections.delete(socket);
229
- });
230
- });
231
-
232
- const closeAllConnections = () => {
233
- for (const socket of connections) {
234
- socket.destroy(); // forcibly closes the connection
235
- connections.delete(socket);
236
- }
237
- };
238
-
239
- const shutdown = async () => {
240
- debug('startServer: onShutdown');
241
-
242
- AdminContext.statsManager.stop();
243
-
244
- await poolsClose(connectionPools);
245
-
246
- server.close(() => {
247
- console.log('Server has closed');
248
- process.exit(0);
249
- });
250
-
251
- closeAllConnections();
252
- };
253
-
254
- // Install shutdown handler
255
- installShutdown(shutdown);
256
-
257
- // Listen
258
- debug('startServer: start listener');
259
- await /** @type {Promise<void>} */ (
260
- new Promise((resolve, reject) => {
261
- server
262
- .listen(internalConfig.port)
263
- .on('listening', () => {
264
- debug('startServer: listener running');
265
- resolve();
266
- })
267
- .on('error', (err) => {
268
- if ('code' in err) {
269
- if (err.code === 'EADDRINUSE') {
270
- err.message = `Port ${internalConfig.port} is already in use`;
271
- } else if (err.code === 'EACCES') {
272
- err.message = `Port ${internalConfig.port} requires elevated privileges`;
273
- }
274
- }
275
- reject(err);
276
- });
277
- })
278
- );
279
-
280
- debug('startServer: END');
281
-
282
- return {
283
- config: internalConfig,
284
- connectionPools,
285
- app,
286
- server,
287
- shutdown,
288
- };
289
- };
290
-
291
- /**
292
- * Load configuration.
293
- * @param {string} [filename] - The configuration filename.
294
- * @returns {configType} - Promise.
295
- */
296
- export const loadConfig = (filename = 'config.json') => /** @type {configType} */ (z$configType.parse(getJsonFile(filename)));
297
-
298
- /**
299
- * Start server from config file.
300
- * @param {string} [filename] - The configuration filename.
301
- * @param {sslConfig} [ssl] - ssl configuration.
302
- * @returns {Promise<webServer>} - Promise resolving to the web server object.
303
- */
304
- export const startServerConfig = async (filename = 'config.json', ssl) => startServer(loadConfig(filename), ssl);
package/src/types.js DELETED
@@ -1,150 +0,0 @@
1
- import z from 'zod';
2
-
3
- /**
4
- * @typedef {import('oracledb').BindParameter} BindParameter
5
- * @typedef {import('oracledb').Connection} Connection
6
- * @typedef {import('express').CookieOptions} CookieOptions
7
- * @typedef {import('express').Request} Request
8
- */
9
-
10
- /**
11
- * @typedef {'basic' | 'debug'} errorStyleType
12
- */
13
- export const z$errorStyleType = z.enum(['basic', 'debug']);
14
-
15
- /**
16
- * @typedef {object} configStaticType
17
- * @property {string} route - The Static route path.
18
- * @property {string} directoryPath - The Static directory.
19
- */
20
- export const z$configStaticType = z.strictObject({
21
- route: z.string(),
22
- directoryPath: z.string(),
23
- });
24
-
25
- /**
26
- * @typedef {(connection: Connection, procedure: string) => void | Promise<void>} transactionCallbackType
27
- * @typedef {'commit' | 'rollback' | transactionCallbackType | undefined | null} transactionModeType
28
- */
29
-
30
- /**
31
- * @typedef {object} configPlSqlHandlerType
32
- * @property {string} defaultPage - The default page.
33
- * @property {string} [pathAlias] - The path alias.
34
- * @property {string} [pathAliasProcedure] - The path alias.
35
- * @property {string} documentTable - The document table.
36
- * @property {string[]} [exclusionList] - The exclusion list.
37
- * @property {string} [requestValidationFunction] - The request validation function.
38
- * @property {Record<string, string>} [cgi] - The additional CGI.
39
- * @property {transactionModeType} [transactionMode] - Specifies an optional transaction mode.
40
- * "commit" this automatically commits any open transaction after each request. This is the defaults because this is what mod_plsql and ohs are doing.
41
- * "rollback" this automatically rolles back any open transaction after each request.
42
- * "transactionCallbackType" this allows to defined a custom handler as a JavaScript function.
43
- * @property {errorStyleType} errorStyle - The error style.
44
- */
45
- export const z$configPlSqlHandlerType = z.strictObject({
46
- defaultPage: z.string(),
47
- pathAlias: z.string().optional(),
48
- pathAliasProcedure: z.string().optional(),
49
- documentTable: z.string(),
50
- exclusionList: z.array(z.string()).optional(),
51
- requestValidationFunction: z.string().optional(),
52
- transactionMode: z.unknown().optional(),
53
- errorStyle: z$errorStyleType,
54
- });
55
-
56
- /**
57
- * @typedef {object} configPlSqlConfigType
58
- * @property {string} route - The PL/SQL route path.
59
- * @property {string} user - The Oracle username.
60
- * @property {string} password - The Oracle password.
61
- * @property {string} connectString - The Oracle connect string.
62
- */
63
- export const z$configPlSqlConfigType = z.strictObject({
64
- route: z.string(),
65
- user: z.string(),
66
- password: z.string(),
67
- connectString: z.string(),
68
- });
69
-
70
- /**
71
- * @typedef {configPlSqlHandlerType & configPlSqlConfigType} configPlSqlType
72
- */
73
- export const z$configPlSqlType = z.strictObject({
74
- ...z$configPlSqlHandlerType.shape,
75
- ...z$configPlSqlConfigType.shape,
76
- });
77
-
78
- /**
79
- * @typedef {object} configType
80
- * @property {number} port - The server port number.
81
- * @property {configStaticType[]} routeStatic - The static routes.
82
- * @property {configPlSqlType[]} routePlSql - The PL/SQL routes.
83
- * @property {number} [uploadFileSizeLimit] - Maximum size of each uploaded file in bytes or no limit if omitted.
84
- * @property {string} loggerFilename - name of the request logger filename or '' if not required.
85
- * @property {string} [adminRoute] - Optional route for the admin console (defaults to /admin).
86
- * @property {string} [adminUser] - Optional username for admin console basic auth.
87
- * @property {string} [adminPassword] - Optional password for admin console basic auth.
88
- */
89
- export const z$configType = z.strictObject({
90
- port: z.number(),
91
- routeStatic: z.array(z$configStaticType),
92
- routePlSql: z.array(z$configPlSqlType),
93
- uploadFileSizeLimit: z.number().optional(),
94
- loggerFilename: z.string(),
95
- adminRoute: z.string().optional(),
96
- adminUser: z.string().optional(),
97
- adminPassword: z.string().optional(),
98
- });
99
-
100
- /**
101
- * Environment variables as string key-value pairs
102
- * @typedef {Record<string, string>} environmentType
103
- */
104
-
105
- /**
106
- * Oracle database bind parameter configuration
107
- * @typedef {Record<string, BindParameter>} BindParameterConfig
108
- */
109
-
110
- /**
111
- * Arguments object with string or string array values
112
- * @typedef {Record<string, string | string[]>} argObjType
113
- */
114
-
115
- /**
116
- * File upload metadata
117
- * @typedef {object} fileUploadType
118
- * @property {string} fieldname - The field value.
119
- * @property {string} originalname - The filename.
120
- * @property {string} encoding - The encoding.
121
- * @property {string} mimetype - The mimetype.
122
- * @property {string} filename - The filename.
123
- * @property {string} path - The path.
124
- * @property {number} size - The size.
125
- */
126
-
127
- /**
128
- * @typedef {object} cookieType
129
- * @property {string} name - The name of the cookie.
130
- * @property {string} value - The value of the cookie.
131
- * @property {CookieOptions} options - The cookie options.
132
- */
133
-
134
- /**
135
- * @typedef {object} pageType - The page.
136
- * @property {string | import('node:stream').Readable} body - The body of the page.
137
- * @property {object} head - The head of the page.
138
- * @property {cookieType[]} head.cookies - The cookies.
139
- * @property {string} [head.contentType] - The content type.
140
- * @property {number} [head.contentLength] - The content length.
141
- * @property {number} [head.statusCode] - The status code.
142
- * @property {string} [head.statusDescription] - The status description.
143
- * @property {string} [head.redirectLocation] - The redirect location.
144
- * @property {Record<string, string>} head.otherHeaders - The other headers.
145
- * @property {string} [head.server] - The server.
146
- * @property {object} file - The file.
147
- * @property {string | null} file.fileType - The file type.
148
- * @property {number | null} file.fileSize - The file size.
149
- * @property {import('node:stream').Readable | Buffer | null} file.fileBlob - The file blob.
150
- */
package/src/util/cache.js DELETED
@@ -1,123 +0,0 @@
1
- /**
2
- * @template T
3
- * @typedef {{hitCount: number, value: T}} cacheEntryType
4
- */
5
-
6
- /**
7
- * Generic Cache class with LFU (Least Frequently Used) eviction policy.
8
- * @template T
9
- */
10
- export class Cache {
11
- /**
12
- * @param {number} maxSize - Maximum number of entries in the cache.
13
- */
14
- constructor(maxSize = 10000) {
15
- /** @type {Map<string, cacheEntryType<T>>} */
16
- this.cache = new Map();
17
- this.maxSize = maxSize;
18
- this.hits = 0;
19
- this.misses = 0;
20
- }
21
-
22
- /**
23
- * Get an entry from the cache.
24
- * @param {string} key - The key.
25
- * @returns {T | undefined} - The value or undefined if not found.
26
- */
27
- get(key) {
28
- const entry = this.cache.get(key);
29
- if (entry) {
30
- entry.hitCount++;
31
- this.hits++;
32
- return entry.value;
33
- }
34
- this.misses++;
35
- return undefined;
36
- }
37
-
38
- /**
39
- * Set an entry in the cache.
40
- * @param {string} key - The key.
41
- * @param {T} value - The value.
42
- */
43
- set(key, value) {
44
- // If updating an existing key, preserve its hitCount?
45
- // Typically LFU implies resetting or keeping.
46
- // For simplicity and avoiding complex aging, if we set it again, we reset hitCount or keep it?
47
- // The requirement is "cache invalidation" (delete) or "cache loading" (set).
48
- // If we overwrite, it's usually a new value. Let's reset hitCount to 0 for a fresh start or 1.
49
-
50
- // Ensure we have space
51
- if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
52
- this.prune();
53
- }
54
-
55
- this.cache.set(key, {hitCount: 0, value});
56
- }
57
-
58
- /**
59
- * Delete an entry from the cache.
60
- * @param {string} key - The key.
61
- */
62
- delete(key) {
63
- this.cache.delete(key);
64
- }
65
-
66
- /**
67
- * Clear the cache.
68
- */
69
- clear() {
70
- this.cache.clear();
71
- this.hits = 0;
72
- this.misses = 0;
73
- }
74
-
75
- /**
76
- * Prune the cache by removing the least frequently used entries.
77
- * Removes 10% of the cache size.
78
- */
79
- prune() {
80
- // Convert cache entries to an array
81
- const entries = Array.from(this.cache.entries());
82
-
83
- // Sort entries by hitCount in ascending order
84
- entries.sort((a, b) => a[1].hitCount - b[1].hitCount);
85
-
86
- // Remove the bottom 10%
87
- const removeCount = Math.max(1, Math.floor(this.maxSize * 0.1));
88
- const keysToRemove = entries.slice(0, removeCount).map(([key]) => key);
89
-
90
- for (const key of keysToRemove) {
91
- this.cache.delete(key);
92
- }
93
- }
94
-
95
- /**
96
- * Get the size of the cache.
97
- * @returns {number} - The size.
98
- */
99
- get size() {
100
- return this.cache.size;
101
- }
102
-
103
- /**
104
- * Get all keys in the cache.
105
- * @returns {string[]} - The keys.
106
- */
107
- keys() {
108
- return Array.from(this.cache.keys());
109
- }
110
-
111
- /**
112
- * Get cache statistics.
113
- * @returns {{size: number, maxSize: number, hits: number, misses: number}} - The statistics.
114
- */
115
- getStats() {
116
- return {
117
- size: this.cache.size,
118
- maxSize: this.maxSize,
119
- hits: this.hits,
120
- misses: this.misses,
121
- };
122
- }
123
- }
@@ -1,24 +0,0 @@
1
- import {inspect} from './trace.js';
2
-
3
- /**
4
- * Convert Error to a string.
5
- *
6
- * @param {unknown} error - The error.
7
- * @returns {string} The string representation.
8
- */
9
- export const errorToString = (error) => {
10
- if (typeof error === 'string') {
11
- return error;
12
- } else if (error instanceof Error) {
13
- const parts = [error.name];
14
- if (typeof error.message === 'string' && error.message.length > 0) {
15
- parts.push(error.message);
16
- }
17
- if (typeof error.stack === 'string' && error.stack.length > 0) {
18
- parts.push(error.stack);
19
- }
20
- return parts.join('\n');
21
- } else {
22
- return inspect(error);
23
- }
24
- };
package/src/util/file.js DELETED
@@ -1,91 +0,0 @@
1
- import {promises as fs, readFileSync} from 'node:fs';
2
-
3
- /**
4
- * Read file.
5
- *
6
- * @param {string} filePath - File name.
7
- * @returns {string} The string.
8
- */
9
- export const readFileSyncUtf8 = (filePath) => {
10
- try {
11
- return readFileSync(filePath, 'utf8');
12
- } catch (err) {
13
- throw new Error(`Unable to read file "${filePath}"`);
14
- }
15
- };
16
-
17
- /**
18
- * Read file.
19
- *
20
- * @param {string} filePath - File name.
21
- * @returns {Promise<Buffer>} The buffer.
22
- */
23
- export const readFile = async (filePath) => {
24
- try {
25
- return await fs.readFile(filePath);
26
- } catch (err) {
27
- throw new Error(`Unable to read file "${filePath}"`);
28
- }
29
- };
30
-
31
- /**
32
- * Remove file.
33
- *
34
- * @param {string} filePath - File name.
35
- * @returns {Promise<void>}.
36
- */
37
- export const removeFile = async (filePath) => {
38
- try {
39
- return await fs.unlink(filePath);
40
- } catch (err) {
41
- throw new Error(`Unable to remove file "${filePath}"`);
42
- }
43
- };
44
-
45
- /**
46
- * Load a json file.
47
- *
48
- * @param {string} filePath - File name.
49
- * @returns {unknown} The json object.
50
- */
51
- export const getJsonFile = (filePath) => {
52
- try {
53
- const fileContent = readFileSync(filePath, 'utf8');
54
- return JSON.parse(fileContent);
55
- } catch (err) {
56
- throw new Error(`Unable to load file "${filePath}"`);
57
- }
58
- };
59
-
60
- /**
61
- * Is this a directory.
62
- * @param {unknown} directoryPath - Directory name.
63
- * @returns {Promise<boolean>} - Return true if it is a directory.
64
- */
65
- export const isDirectory = async (directoryPath) => {
66
- if (typeof directoryPath !== 'string') {
67
- return false;
68
- }
69
-
70
- const stats = await fs.stat(directoryPath);
71
-
72
- return stats.isDirectory();
73
- };
74
-
75
- /**
76
- * Is this a file.
77
- * @param {unknown} filePath - File name.
78
- * @returns {Promise<boolean>} - Return true if it is a file.
79
- */
80
- export const isFile = async (filePath) => {
81
- if (typeof filePath !== 'string') {
82
- return false;
83
- }
84
-
85
- try {
86
- const stats = await fs.stat(filePath);
87
- return stats.isFile();
88
- } catch {
89
- return false;
90
- }
91
- };