@ruiapp/rapid-core 0.9.6 → 0.9.8

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.
@@ -0,0 +1,12 @@
1
+ import { ActionHandlerContext } from "../actionHandler";
2
+ declare const _default: {
3
+ namespace: string;
4
+ name: string;
5
+ code: string;
6
+ type: "RESTful";
7
+ method: "GET";
8
+ endpoint: string;
9
+ handler: typeof handler;
10
+ };
11
+ export default _default;
12
+ export declare function handler(ctx: ActionHandlerContext): Promise<void>;
@@ -0,0 +1,10 @@
1
+ declare const _default: {
2
+ namespace: string;
3
+ name: string;
4
+ code: string;
5
+ type: "RESTful";
6
+ method: "GET";
7
+ endpoint: string;
8
+ handler: typeof import("./healthz").handler;
9
+ }[];
10
+ export default _default;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { default as EntityManager } from "./dataAccess/entityManager";
13
13
  export * from "./dataAccess/entityManager";
14
14
  export * from "./utilities/accessControlUtility";
15
15
  export * from "./utilities/entityUtility";
16
+ export * from "./utilities/fsUtility";
16
17
  export * from "./utilities/jwtUtility";
17
18
  export * from "./utilities/timeUtility";
18
19
  export * from "./utilities/passwordUtility";
package/dist/index.js CHANGED
@@ -7,11 +7,11 @@ var events = require('events');
7
7
  var Router = require('koa-tree-router');
8
8
  var qs = require('qs');
9
9
  var dayjs = require('dayjs');
10
+ var fs = require('fs');
11
+ var path = require('path');
10
12
  var jsonwebtoken = require('jsonwebtoken');
11
13
  var crypto = require('crypto');
12
14
  var bcrypt = require('bcryptjs');
13
- var path = require('path');
14
- var fs = require('fs');
15
15
  var uuid = require('uuid');
16
16
  var nodemailer = require('nodemailer');
17
17
  var cron = require('cron');
@@ -22,10 +22,10 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
22
22
  var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
23
23
  var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
24
24
  var dayjs__default = /*#__PURE__*/_interopDefaultLegacy(dayjs);
25
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
26
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
25
27
  var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
26
28
  var bcrypt__default = /*#__PURE__*/_interopDefaultLegacy(bcrypt);
27
- var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
28
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
29
29
  var nodemailer__default = /*#__PURE__*/_interopDefaultLegacy(nodemailer);
30
30
 
31
31
  function fixBigIntJSONSerialize() {
@@ -1032,17 +1032,37 @@ async function buildRoutes(server, applicationConfig) {
1032
1032
  input,
1033
1033
  };
1034
1034
  await server.beforeRunRouteActions(handlerContext);
1035
- for (const actionConfig of routeConfig.actions) {
1036
- const actionCode = actionConfig.code;
1037
- const handler = server.getActionHandlerByCode(actionCode);
1038
- if (!handler) {
1039
- throw new Error("Unknown handler: " + actionCode);
1035
+ let handler = routeConfig.handler;
1036
+ if (handler) {
1037
+ if (lodash.isString(handler)) {
1038
+ handler = new Function(`return (${routeConfig.handler})`);
1040
1039
  }
1041
- await server.beforeRunActionHandler(handlerContext, actionConfig);
1042
- const result = handler(handlerContext, actionConfig.config);
1043
- if (result instanceof Promise) {
1044
- await result;
1040
+ if (lodash.isFunction(handler)) {
1041
+ const result = handler(handlerContext);
1042
+ if (result instanceof Promise) {
1043
+ await result;
1044
+ }
1045
1045
  }
1046
+ else {
1047
+ throw new Error(`Invalid handler for route ${routeConfig.code}: ${routeConfig.handler}`);
1048
+ }
1049
+ }
1050
+ else if (routeConfig.actions) {
1051
+ for (const actionConfig of routeConfig.actions) {
1052
+ const actionCode = actionConfig.code;
1053
+ const handler = server.getActionHandlerByCode(actionCode);
1054
+ if (!handler) {
1055
+ throw new Error("Unknown handler: " + actionCode);
1056
+ }
1057
+ await server.beforeRunActionHandler(handlerContext, actionConfig);
1058
+ const result = handler(handlerContext, actionConfig.config);
1059
+ if (result instanceof Promise) {
1060
+ await result;
1061
+ }
1062
+ }
1063
+ }
1064
+ else {
1065
+ throw new Error(`No handler or actions defined for route ${routeConfig.code}`);
1046
1066
  }
1047
1067
  if (!isNullOrUndefined(handlerContext.output)) {
1048
1068
  routerContext.json(handlerContext.output, handlerContext.status);
@@ -3963,6 +3983,21 @@ class EntityManager {
3963
3983
  }
3964
3984
  }
3965
3985
 
3986
+ var healthz = {
3987
+ namespace: "sys",
3988
+ name: "sys.healthz",
3989
+ code: "sys.healthz",
3990
+ type: "RESTful",
3991
+ method: "GET",
3992
+ endpoint: "/healthz",
3993
+ handler: handler$x,
3994
+ };
3995
+ async function handler$x(ctx) {
3996
+ ctx.output = {};
3997
+ }
3998
+
3999
+ var coreRoutes = [healthz];
4000
+
3966
4001
  class RapidServer {
3967
4002
  #logger;
3968
4003
  #facilityFactories;
@@ -4237,7 +4272,12 @@ class RapidServer {
4237
4272
  this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4238
4273
  }
4239
4274
  }
4275
+ this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
4276
+ this.appendApplicationConfig({
4277
+ routes: coreRoutes,
4278
+ });
4240
4279
  await this.configureApplication();
4280
+ this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
4241
4281
  if (!this.#disableCronJobs) {
4242
4282
  await pluginManager.registerCronJobs();
4243
4283
  }
@@ -4245,7 +4285,6 @@ class RapidServer {
4245
4285
  await pluginManager.onApplicationReady(this.#applicationConfig);
4246
4286
  }
4247
4287
  async configureApplication() {
4248
- this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
4249
4288
  const pluginManager = this.#pluginManager;
4250
4289
  await pluginManager.onLoadingApplication(this.#applicationConfig);
4251
4290
  await pluginManager.configureModels(this.#applicationConfig);
@@ -4254,7 +4293,6 @@ class RapidServer {
4254
4293
  await pluginManager.configureRoutes(this.#applicationConfig);
4255
4294
  // TODO: check application configuration.
4256
4295
  await pluginManager.onApplicationLoaded(this.#applicationConfig);
4257
- this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
4258
4296
  }
4259
4297
  registerFacilityFactory(factory) {
4260
4298
  this.#facilityFactories.set(factory.name, factory);
@@ -4903,6 +4941,128 @@ function getEntityRelationTargetId(entity, propName, targetColumnName) {
4903
4941
  }
4904
4942
  }
4905
4943
 
4944
+ async function readFile(path) {
4945
+ return new Promise((resolve, reject) => {
4946
+ fs__default["default"].readFile(path, null, (err, data) => {
4947
+ if (err) {
4948
+ reject(err);
4949
+ }
4950
+ else {
4951
+ resolve(data);
4952
+ }
4953
+ });
4954
+ });
4955
+ }
4956
+ async function copyFile(fromPath, toPath) {
4957
+ return new Promise((resolve, reject) => {
4958
+ fs__default["default"].copyFile(fromPath, toPath, (err) => {
4959
+ if (err) {
4960
+ reject(err);
4961
+ }
4962
+ else {
4963
+ resolve();
4964
+ }
4965
+ });
4966
+ });
4967
+ }
4968
+ async function removeFile(path) {
4969
+ return new Promise((resolve, reject) => {
4970
+ fs__default["default"].rm(path, (err) => {
4971
+ if (err) {
4972
+ reject(err);
4973
+ }
4974
+ else {
4975
+ resolve();
4976
+ }
4977
+ });
4978
+ });
4979
+ }
4980
+ async function moveFile(fromPath, toPath) {
4981
+ return new Promise((resolve, reject) => {
4982
+ fs__default["default"].rename(fromPath, toPath, (err) => {
4983
+ if (err) {
4984
+ reject(err);
4985
+ }
4986
+ else {
4987
+ resolve();
4988
+ }
4989
+ });
4990
+ });
4991
+ }
4992
+ async function appendFile(path, data) {
4993
+ return new Promise((resolve, reject) => {
4994
+ fs__default["default"].appendFile(path, Buffer.from(data), (err) => {
4995
+ if (err) {
4996
+ reject(err);
4997
+ }
4998
+ else {
4999
+ resolve();
5000
+ }
5001
+ });
5002
+ });
5003
+ }
5004
+ async function writeFile(path, data) {
5005
+ return new Promise((resolve, reject) => {
5006
+ fs__default["default"].writeFile(path, Buffer.from(data), (err) => {
5007
+ if (err) {
5008
+ reject(err);
5009
+ }
5010
+ else {
5011
+ resolve();
5012
+ }
5013
+ });
5014
+ });
5015
+ }
5016
+ function ensureDirectoryExists(dirPath) {
5017
+ if (!fs__default["default"].existsSync(dirPath)) {
5018
+ const parentDirPath = path__default["default"].dirname(dirPath);
5019
+ if (parentDirPath == dirPath) {
5020
+ return;
5021
+ }
5022
+ ensureDirectoryExists(parentDirPath);
5023
+ fs__default["default"].mkdirSync(dirPath);
5024
+ }
5025
+ }
5026
+ function enumFileBaseNamesInDirectory(options) {
5027
+ const { dirPath, prefix, fileNameFilter } = options;
5028
+ let fileNames = [];
5029
+ let resolvedDirPath = dirPath;
5030
+ const isRelative = dirPath.startsWith(".") || dirPath.startsWith("..");
5031
+ if (isRelative) {
5032
+ resolvedDirPath = path__default["default"].join(process.cwd(), dirPath);
5033
+ }
5034
+ if (!fs__default["default"].existsSync(resolvedDirPath)) {
5035
+ console.warn(`Directory '${resolvedDirPath}' not found.`);
5036
+ return [];
5037
+ }
5038
+ const files = fs__default["default"].readdirSync(resolvedDirPath);
5039
+ for (const fileName of files) {
5040
+ const filePathName = path__default["default"].join(resolvedDirPath, fileName);
5041
+ const fileStat = fs__default["default"].statSync(filePathName);
5042
+ if (fileStat.isDirectory()) {
5043
+ fileNames = fileNames.concat(enumFileBaseNamesInDirectory({
5044
+ dirPath: filePathName,
5045
+ prefix: prefix ? `${prefix}/${fileName}` : fileName,
5046
+ fileNameFilter,
5047
+ }));
5048
+ }
5049
+ else if (fileStat.isFile()) {
5050
+ if (fileNameFilter && !fileNameFilter(fileName)) {
5051
+ continue;
5052
+ }
5053
+ const baseName = path__default["default"].parse(fileName).name;
5054
+ if (prefix) {
5055
+ fileNames.push(`${prefix}/${baseName}`);
5056
+ }
5057
+ else {
5058
+ fileNames.push(baseName);
5059
+ }
5060
+ }
5061
+ }
5062
+ fileNames.sort();
5063
+ return fileNames;
5064
+ }
5065
+
4906
5066
  // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
4907
5067
  // This module is browser compatible.
4908
5068
  /**
@@ -7335,31 +7495,6 @@ class AuthPlugin {
7335
7495
  }
7336
7496
  }
7337
7497
 
7338
- async function readFile(path) {
7339
- return new Promise((resolve, reject) => {
7340
- fs__default["default"].readFile(path, null, (err, data) => {
7341
- if (err) {
7342
- reject(err);
7343
- }
7344
- else {
7345
- resolve(data);
7346
- }
7347
- });
7348
- });
7349
- }
7350
- async function appendFile(path, data) {
7351
- return new Promise((resolve, reject) => {
7352
- fs__default["default"].appendFile(path, Buffer.from(data), (err) => {
7353
- if (err) {
7354
- reject(err);
7355
- }
7356
- else {
7357
- resolve();
7358
- }
7359
- });
7360
- });
7361
- }
7362
-
7363
7498
  function getFileBaseName(pathname) {
7364
7499
  const extName = path__default["default"].extname(pathname);
7365
7500
  return path__default["default"].basename(pathname, extName);
@@ -7473,7 +7608,7 @@ async function handler$8(plugin, ctx, options) {
7473
7608
  const fileKey = `${uuid.v1()}${extName}`;
7474
7609
  const filePathName = path__default["default"].join(server.config.localFileStoragePath, fileKey);
7475
7610
  const fileBuffer = await file.arrayBuffer();
7476
- await appendFile(filePathName, fileBuffer);
7611
+ await writeFile(filePathName, fileBuffer);
7477
7612
  ctx.output = { ok: true, fileKey };
7478
7613
  }
7479
7614
 
@@ -9668,12 +9803,16 @@ exports.ServerOperationPlugin = ServerOperationPlugin;
9668
9803
  exports.SettingPlugin = SettingPlugin;
9669
9804
  exports.StateMachinePlugin = StateMachinePlugin;
9670
9805
  exports.WebhooksPlugin = WebhooksPlugin;
9806
+ exports.appendFile = appendFile;
9671
9807
  exports.bootstrapApplicationConfig = bootstrapApplicationConfig$1;
9808
+ exports.copyFile = copyFile;
9672
9809
  exports.createJwt = createJwt;
9673
9810
  exports.decodeJwt = decodeJwt;
9674
9811
  exports.deleteCookie = deleteCookie;
9675
9812
  exports.detectChangedFieldsOfEntity = detectChangedFieldsOfEntity;
9813
+ exports.ensureDirectoryExists = ensureDirectoryExists;
9676
9814
  exports.entityHelper = entityHelper;
9815
+ exports.enumFileBaseNamesInDirectory = enumFileBaseNamesInDirectory;
9677
9816
  exports.formatDateTimeWithTimezone = formatDateTimeWithTimezone;
9678
9817
  exports.generateJwtSecretKey = generateJwtSecretKey;
9679
9818
  exports.generatePasswordHash = generatePasswordHash;
@@ -9687,8 +9826,12 @@ exports.isAccessAllowed = isAccessAllowed;
9687
9826
  exports.licenseHelper = licenseHelper;
9688
9827
  exports.mapDbRowToEntity = mapDbRowToEntity;
9689
9828
  exports.metaHelper = metaHelper;
9829
+ exports.moveFile = moveFile;
9830
+ exports.readFile = readFile;
9831
+ exports.removeFile = removeFile;
9690
9832
  exports.setCookie = setCookie;
9691
9833
  exports.tryValidateLicense = tryValidateLicense;
9692
9834
  exports.validateLicense = validateLicense;
9693
9835
  exports.validatePassword = validatePassword;
9694
9836
  exports.verifyJwt = verifyJwt;
9837
+ exports.writeFile = writeFile;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ActionHandlerContext } from "./core/actionHandler";
1
2
  import { RouteContext } from "./core/routeContext";
2
3
  import { IRpdServer, RapidPlugin } from "./core/server";
3
4
  import { ColumnSelectOptions, CountRowOptions, FindRowOptions, RowFilterOptions } from "./dataAccess/dataAccessTypes";
@@ -483,6 +484,7 @@ export interface RpdRoute {
483
484
  method: RpdHttpMethod;
484
485
  endpoint: string;
485
486
  actions?: RpdRouteActionConfig[];
487
+ handler?: string | ((ctx: ActionHandlerContext) => Promise<void>);
486
488
  }
487
489
  export type RpdHttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
488
490
  export interface RpdRouteActionConfig {
@@ -4,3 +4,11 @@ export declare function copyFile(fromPath: string, toPath: string): Promise<void
4
4
  export declare function removeFile(path: string): Promise<void>;
5
5
  export declare function moveFile(fromPath: string, toPath: string): Promise<void>;
6
6
  export declare function appendFile(path: string, data: ArrayBuffer): Promise<void>;
7
+ export declare function writeFile(path: string, data: ArrayBuffer): Promise<void>;
8
+ export declare function ensureDirectoryExists(dirPath: string): void;
9
+ export type EnumFileBaseNamesOptions = {
10
+ dirPath: string;
11
+ prefix?: string;
12
+ fileNameFilter?: (fileName: string) => boolean;
13
+ };
14
+ export declare function enumFileBaseNamesInDirectory(options: EnumFileBaseNamesOptions): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,20 @@
1
+ import { RpdRoute } from "~/types";
2
+ import { ActionHandlerContext } from "../actionHandler";
3
+
4
+ export default {
5
+ namespace: "sys",
6
+ name: "sys.healthz",
7
+ code: "sys.healthz",
8
+ type: "RESTful",
9
+ method: "GET",
10
+ endpoint: "/healthz",
11
+ handler,
12
+ } satisfies RpdRoute;
13
+
14
+ export async function handler(ctx: ActionHandlerContext) {
15
+ const { server, input, routerContext: routeContext, logger } = ctx;
16
+ const { response } = routeContext;
17
+ const {} = input;
18
+
19
+ ctx.output = {};
20
+ }
@@ -0,0 +1,3 @@
1
+ import healthz from "./healthz";
2
+
3
+ export default [healthz];
@@ -5,7 +5,7 @@ import { IRpdServer } from "~/core/server";
5
5
  import { RpdApplicationConfig } from "~/types";
6
6
  import { isNullOrUndefined } from "~/utilities/typeUtility";
7
7
  import { Next, RouteContext } from "./routeContext";
8
- import { cloneDeep } from "lodash";
8
+ import { cloneDeep, isFunction, isString } from "lodash";
9
9
 
10
10
  export async function buildRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig) {
11
11
  const logger = server.getLogger();
@@ -67,19 +67,37 @@ export async function buildRoutes(server: IRpdServer, applicationConfig: RpdAppl
67
67
 
68
68
  await server.beforeRunRouteActions(handlerContext);
69
69
 
70
- for (const actionConfig of routeConfig.actions) {
71
- const actionCode = actionConfig.code;
72
- const handler = server.getActionHandlerByCode(actionCode);
73
- if (!handler) {
74
- throw new Error("Unknown handler: " + actionCode);
70
+ let handler = routeConfig.handler as any;
71
+ if (handler) {
72
+ if (isString(handler)) {
73
+ handler = new Function(`return (${routeConfig.handler})`) as any;
75
74
  }
76
75
 
77
- await server.beforeRunActionHandler(handlerContext, actionConfig);
78
-
79
- const result = handler(handlerContext, actionConfig.config);
80
- if (result instanceof Promise) {
81
- await result;
76
+ if (isFunction(handler)) {
77
+ const result = handler(handlerContext);
78
+ if (result instanceof Promise) {
79
+ await result;
80
+ }
81
+ } else {
82
+ throw new Error(`Invalid handler for route ${routeConfig.code}: ${routeConfig.handler}`);
83
+ }
84
+ } else if (routeConfig.actions) {
85
+ for (const actionConfig of routeConfig.actions) {
86
+ const actionCode = actionConfig.code;
87
+ const handler = server.getActionHandlerByCode(actionCode);
88
+ if (!handler) {
89
+ throw new Error("Unknown handler: " + actionCode);
90
+ }
91
+
92
+ await server.beforeRunActionHandler(handlerContext, actionConfig);
93
+
94
+ const result = handler(handlerContext, actionConfig.config);
95
+ if (result instanceof Promise) {
96
+ await result;
97
+ }
82
98
  }
99
+ } else {
100
+ throw new Error(`No handler or actions defined for route ${routeConfig.code}`);
83
101
  }
84
102
 
85
103
  if (!isNullOrUndefined(handlerContext.output)) {
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export * from "./dataAccess/entityManager";
20
20
 
21
21
  export * from "./utilities/accessControlUtility";
22
22
  export * from "./utilities/entityUtility";
23
+ export * from "./utilities/fsUtility";
23
24
  export * from "./utilities/jwtUtility";
24
25
  export * from "./utilities/timeUtility";
25
26
  export * from "./utilities/passwordUtility";
@@ -1,5 +1,5 @@
1
1
  import { v1 as uuidv1 } from "uuid";
2
- import { appendFile } from "~/utilities/fsUtility";
2
+ import { writeFile } from "~/utilities/fsUtility";
3
3
  import { ActionHandlerContext } from "~/core/actionHandler";
4
4
  import path from "path";
5
5
  import { isArray } from "lodash";
@@ -27,7 +27,7 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
27
27
  const filePathName = path.join(server.config.localFileStoragePath, fileKey);
28
28
 
29
29
  const fileBuffer = await file.arrayBuffer();
30
- await appendFile(filePathName, fileBuffer);
30
+ await writeFile(filePathName, fileBuffer);
31
31
 
32
32
  ctx.output = { ok: true, fileKey };
33
33
  }
package/src/server.ts CHANGED
@@ -35,6 +35,7 @@ import { bind, cloneDeep, find, forEach, isString, merge, omit } from "lodash";
35
35
  import { Logger } from "./facilities/log/LogFacility";
36
36
  import { FacilityFactory } from "./core/facility";
37
37
  import { CronJobConfiguration } from "./types/cron-job-types";
38
+ import coreRoutes from "./core/routes";
38
39
 
39
40
  export interface InitServerOptions {
40
41
  logger: Logger;
@@ -367,7 +368,12 @@ export class RapidServer implements IRpdServer {
367
368
  }
368
369
  }
369
370
 
371
+ this.#applicationConfig = cloneDeep(this.#bootstrapApplicationConfig) as RpdApplicationConfig;
372
+ this.appendApplicationConfig({
373
+ routes: coreRoutes,
374
+ });
370
375
  await this.configureApplication();
376
+ this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
371
377
 
372
378
  if (!this.#disableCronJobs) {
373
379
  await pluginManager.registerCronJobs();
@@ -378,8 +384,6 @@ export class RapidServer implements IRpdServer {
378
384
  }
379
385
 
380
386
  async configureApplication() {
381
- this.#applicationConfig = cloneDeep(this.#bootstrapApplicationConfig) as RpdApplicationConfig;
382
-
383
387
  const pluginManager = this.#pluginManager;
384
388
  await pluginManager.onLoadingApplication(this.#applicationConfig);
385
389
  await pluginManager.configureModels(this.#applicationConfig);
@@ -390,8 +394,6 @@ export class RapidServer implements IRpdServer {
390
394
  // TODO: check application configuration.
391
395
 
392
396
  await pluginManager.onApplicationLoaded(this.#applicationConfig);
393
-
394
- this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
395
397
  }
396
398
 
397
399
  registerFacilityFactory(factory: FacilityFactory) {
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ActionHandlerContext } from "./core/actionHandler";
1
2
  import { RouteContext } from "./core/routeContext";
2
3
  import { IRpdServer, RapidPlugin } from "./core/server";
3
4
  import { ColumnSelectOptions, CountRowOptions, FindRowOptions, RowFilterOptions } from "./dataAccess/dataAccessTypes";
@@ -586,6 +587,7 @@ export interface RpdRoute {
586
587
  method: RpdHttpMethod;
587
588
  endpoint: string;
588
589
  actions?: RpdRouteActionConfig[];
590
+ handler?: string | ((ctx: ActionHandlerContext) => Promise<void>);
589
591
  }
590
592
 
591
593
  export type RpdHttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
 
3
4
  export async function readFile(path: string): Promise<Buffer> {
4
5
  return new Promise((resolve, reject) => {
@@ -59,3 +60,78 @@ export async function appendFile(path: string, data: ArrayBuffer): Promise<void>
59
60
  });
60
61
  });
61
62
  }
63
+
64
+ export async function writeFile(path: string, data: ArrayBuffer): Promise<void> {
65
+ return new Promise((resolve, reject) => {
66
+ fs.writeFile(path, Buffer.from(data), (err) => {
67
+ if (err) {
68
+ reject(err);
69
+ } else {
70
+ resolve();
71
+ }
72
+ });
73
+ });
74
+ }
75
+
76
+ export function ensureDirectoryExists(dirPath: string) {
77
+ if (!fs.existsSync(dirPath)) {
78
+ const parentDirPath = path.dirname(dirPath);
79
+ if (parentDirPath == dirPath) {
80
+ return;
81
+ }
82
+ ensureDirectoryExists(parentDirPath);
83
+
84
+ fs.mkdirSync(dirPath);
85
+ }
86
+ }
87
+
88
+ export type EnumFileBaseNamesOptions = {
89
+ dirPath: string;
90
+ prefix?: string;
91
+ fileNameFilter?: (fileName: string) => boolean;
92
+ };
93
+
94
+ export function enumFileBaseNamesInDirectory(options: EnumFileBaseNamesOptions): string[] {
95
+ const { dirPath, prefix, fileNameFilter } = options;
96
+ let fileNames = [];
97
+
98
+ let resolvedDirPath = dirPath;
99
+ const isRelative = dirPath.startsWith(".") || dirPath.startsWith("..");
100
+ if (isRelative) {
101
+ resolvedDirPath = path.join(process.cwd(), dirPath);
102
+ }
103
+
104
+ if (!fs.existsSync(resolvedDirPath)) {
105
+ console.warn(`Directory '${resolvedDirPath}' not found.`);
106
+ return [];
107
+ }
108
+
109
+ const files = fs.readdirSync(resolvedDirPath);
110
+ for (const fileName of files) {
111
+ const filePathName = path.join(resolvedDirPath, fileName);
112
+ const fileStat = fs.statSync(filePathName);
113
+ if (fileStat.isDirectory()) {
114
+ fileNames = fileNames.concat(
115
+ enumFileBaseNamesInDirectory({
116
+ dirPath: filePathName,
117
+ prefix: prefix ? `${prefix}/${fileName}` : fileName,
118
+ fileNameFilter,
119
+ }),
120
+ );
121
+ } else if (fileStat.isFile()) {
122
+ if (fileNameFilter && !fileNameFilter(fileName)) {
123
+ continue;
124
+ }
125
+
126
+ const baseName = path.parse(fileName).name;
127
+ if (prefix) {
128
+ fileNames.push(`${prefix}/${baseName}`);
129
+ } else {
130
+ fileNames.push(baseName);
131
+ }
132
+ }
133
+ }
134
+
135
+ fileNames.sort();
136
+ return fileNames;
137
+ }