@loomcore/api 0.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 (83) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +35 -0
  2. package/dist/__tests__/common-test.utils.js +181 -0
  3. package/dist/__tests__/test-express-app.d.ts +16 -0
  4. package/dist/__tests__/test-express-app.js +83 -0
  5. package/dist/config/api-common-config.d.ts +3 -0
  6. package/dist/config/api-common-config.js +11 -0
  7. package/dist/config/index.d.ts +1 -0
  8. package/dist/config/index.js +1 -0
  9. package/dist/controllers/api-controller.utils.d.ts +1 -0
  10. package/dist/controllers/api-controller.utils.js +1 -0
  11. package/dist/controllers/api.controller.d.ts +22 -0
  12. package/dist/controllers/api.controller.js +71 -0
  13. package/dist/controllers/auth.controller.d.ts +16 -0
  14. package/dist/controllers/auth.controller.js +73 -0
  15. package/dist/controllers/index.d.ts +1 -0
  16. package/dist/controllers/index.js +1 -0
  17. package/dist/errors/bad-request.error.d.ts +9 -0
  18. package/dist/errors/bad-request.error.js +12 -0
  19. package/dist/errors/database-connection.error.d.ts +9 -0
  20. package/dist/errors/database-connection.error.js +12 -0
  21. package/dist/errors/duplicate-key.error.d.ts +9 -0
  22. package/dist/errors/duplicate-key.error.js +11 -0
  23. package/dist/errors/id-not-found.error.d.ts +9 -0
  24. package/dist/errors/id-not-found.error.js +11 -0
  25. package/dist/errors/index.d.ts +8 -0
  26. package/dist/errors/index.js +8 -0
  27. package/dist/errors/not-found.error.d.ts +9 -0
  28. package/dist/errors/not-found.error.js +12 -0
  29. package/dist/errors/server.error.d.ts +9 -0
  30. package/dist/errors/server.error.js +11 -0
  31. package/dist/errors/unauthenticated.error.d.ts +8 -0
  32. package/dist/errors/unauthenticated.error.js +11 -0
  33. package/dist/errors/unauthorized.error.d.ts +8 -0
  34. package/dist/errors/unauthorized.error.js +11 -0
  35. package/dist/middleware/ensure-user-context.d.ts +2 -0
  36. package/dist/middleware/ensure-user-context.js +7 -0
  37. package/dist/middleware/error-handler.d.ts +2 -0
  38. package/dist/middleware/error-handler.js +30 -0
  39. package/dist/middleware/index.d.ts +3 -0
  40. package/dist/middleware/index.js +3 -0
  41. package/dist/middleware/is-authenticated.d.ts +2 -0
  42. package/dist/middleware/is-authenticated.js +27 -0
  43. package/dist/models/api-common-config.interface.d.ts +22 -0
  44. package/dist/models/api-common-config.interface.js +1 -0
  45. package/dist/models/base-api-config.interface.d.ts +12 -0
  46. package/dist/models/base-api-config.interface.js +1 -0
  47. package/dist/models/index.d.ts +3 -0
  48. package/dist/models/index.js +3 -0
  49. package/dist/models/types/index.d.ts +1 -0
  50. package/dist/models/types/index.js +1 -0
  51. package/dist/services/auth.service.d.ts +54 -0
  52. package/dist/services/auth.service.js +283 -0
  53. package/dist/services/email.service.d.ts +4 -0
  54. package/dist/services/email.service.js +24 -0
  55. package/dist/services/generic-api-service.interface.d.ts +18 -0
  56. package/dist/services/generic-api-service.interface.js +1 -0
  57. package/dist/services/generic-api.service.d.ts +44 -0
  58. package/dist/services/generic-api.service.js +378 -0
  59. package/dist/services/index.d.ts +8 -0
  60. package/dist/services/index.js +8 -0
  61. package/dist/services/jwt.service.d.ts +4 -0
  62. package/dist/services/jwt.service.js +18 -0
  63. package/dist/services/multi-tenant-api.service.d.ts +10 -0
  64. package/dist/services/multi-tenant-api.service.js +31 -0
  65. package/dist/services/password-reset-token.service.d.ts +8 -0
  66. package/dist/services/password-reset-token.service.js +20 -0
  67. package/dist/services/tenant-query-decorator.d.ts +14 -0
  68. package/dist/services/tenant-query-decorator.js +66 -0
  69. package/dist/utils/address.utils.d.ts +6 -0
  70. package/dist/utils/address.utils.js +15 -0
  71. package/dist/utils/api.utils.d.ts +17 -0
  72. package/dist/utils/api.utils.js +60 -0
  73. package/dist/utils/conversion.utils.d.ts +5 -0
  74. package/dist/utils/conversion.utils.js +14 -0
  75. package/dist/utils/db.utils.d.ts +27 -0
  76. package/dist/utils/db.utils.js +273 -0
  77. package/dist/utils/index.d.ts +6 -0
  78. package/dist/utils/index.js +6 -0
  79. package/dist/utils/password.utils.d.ts +7 -0
  80. package/dist/utils/password.utils.js +23 -0
  81. package/dist/utils/string.utils.d.ts +9 -0
  82. package/dist/utils/string.utils.js +30 -0
  83. package/package.json +71 -0
@@ -0,0 +1,60 @@
1
+ import { QueryOptions } from '@loomcore/common/models';
2
+ function apiResponse(response, status, options = {}, modelSpec, publicSchema) {
3
+ const success = status >= 200 && status < 300;
4
+ let apiResponse;
5
+ if (modelSpec && options.data) {
6
+ if (Array.isArray(options.data)) {
7
+ options.data = options.data.map((item) => modelSpec.encode(item, publicSchema));
8
+ }
9
+ else if (typeof options.data === 'object' && options.data !== null && 'entities' in options.data && Array.isArray(options.data.entities)) {
10
+ const pagedResult = options.data;
11
+ pagedResult.entities = pagedResult.entities.map((item) => modelSpec.encode(item, publicSchema));
12
+ options.data = pagedResult;
13
+ }
14
+ else {
15
+ options.data = modelSpec.encode(options.data, publicSchema);
16
+ }
17
+ }
18
+ if (success) {
19
+ apiResponse = {
20
+ success,
21
+ status,
22
+ data: options?.data,
23
+ messages: options?.messages,
24
+ errors: options.errors,
25
+ };
26
+ }
27
+ else {
28
+ apiResponse = {
29
+ success,
30
+ status: status,
31
+ errors: options.errors,
32
+ };
33
+ }
34
+ return response.status(status).json(apiResponse);
35
+ }
36
+ function getQueryOptionsFromRequest(request) {
37
+ const queryOptions = {
38
+ orderBy: request.query.orderBy,
39
+ sortDirection: request.query.sortDirection,
40
+ page: request.query.page ? parseInt(request.query.page) : 1,
41
+ pageSize: request.query.pageSize ? parseInt(request.query.pageSize) : 100,
42
+ filters: request.query.filters
43
+ };
44
+ return new QueryOptions(queryOptions);
45
+ }
46
+ function getPagedResult(entities, totalRows, queryOptions) {
47
+ const pagedResult = {
48
+ entities,
49
+ total: totalRows,
50
+ page: queryOptions.page,
51
+ pageSize: queryOptions.pageSize,
52
+ totalPages: Math.ceil(totalRows / queryOptions.pageSize),
53
+ };
54
+ return pagedResult;
55
+ }
56
+ export const apiUtils = {
57
+ apiResponse,
58
+ getQueryOptionsFromRequest,
59
+ getPagedResult,
60
+ };
@@ -0,0 +1,5 @@
1
+ declare function convertISOStringDateTimesToJSDates(objectToConvert: any): void;
2
+ export declare const conversionUtils: {
3
+ convertISOStringDateTimesToJSDates: typeof convertISOStringDateTimesToJSDates;
4
+ };
5
+ export {};
@@ -0,0 +1,14 @@
1
+ function convertISOStringDateTimesToJSDates(objectToConvert) {
2
+ var iso8601RegEx = /((((\d{4})(-((0[1-9])|(1[012])))(-((0[1-9])|([12]\d)|(3[01]))))(T((([01]\d)|(2[0123]))((:([012345]\d))((:([012345]\d))(\.(\d+))?)?)?)(Z|([\+\-](([01]\d)|(2[0123]))(:([012345]\d))?)))?)|(((\d{4})((0[1-9])|(1[012]))((0[1-9])|([12]\d)|(3[01])))(T((([01]\d)|(2[0123]))(([012345]\d)(([012345]\d)(\d+)?)?)?)(Z|([\+\-](([01]\d)|(2[0123]))([012345]\d)?)))?))/;
3
+ for (var property in objectToConvert) {
4
+ if (objectToConvert.hasOwnProperty(property)) {
5
+ if (iso8601RegEx.test(objectToConvert[property])) {
6
+ var date = new Date(objectToConvert[property]);
7
+ objectToConvert[property] = date;
8
+ }
9
+ }
10
+ }
11
+ }
12
+ export const conversionUtils = {
13
+ convertISOStringDateTimesToJSDates
14
+ };
@@ -0,0 +1,27 @@
1
+ import { ObjectId } from 'mongodb';
2
+ import { TSchema } from '@sinclair/typebox';
3
+ import { IQueryOptions, Filter } from '@loomcore/common/models';
4
+ export declare const PROPERTIES_THAT_ARE_NOT_OBJECT_IDS: string[];
5
+ declare function convertObjectIdsToStrings<T>(entity: T, schema?: TSchema): T;
6
+ declare function convertStringsToObjectIds(entity: any, schema: TSchema): any;
7
+ declare function convertStringToObjectId(value: any): ObjectId | any;
8
+ declare function buildMongoMatchFromQueryOptions(queryOptions: IQueryOptions): {
9
+ $match: any;
10
+ };
11
+ declare function buildSQLWhereClauseFromQueryOptions(queryOptions: IQueryOptions, columnAliasMap: {
12
+ [key: string]: string;
13
+ }): string;
14
+ declare function addKeyValueToWhereClause(whereClause: string, key: string, value: Filter, tableAlias?: string): string;
15
+ declare function appendToWhereClause(whereClause: string, condition: string): string;
16
+ declare function formatValue(value: string | number | boolean | Date, isLikeOperator?: boolean): string;
17
+ export declare const dbUtils: {
18
+ buildMongoMatchFromQueryOptions: typeof buildMongoMatchFromQueryOptions;
19
+ buildSQLWhereClauseFromQueryOptions: typeof buildSQLWhereClauseFromQueryOptions;
20
+ addKeyValueToWhereClause: typeof addKeyValueToWhereClause;
21
+ appendToWhereClause: typeof appendToWhereClause;
22
+ formatValue: typeof formatValue;
23
+ convertStringToObjectId: typeof convertStringToObjectId;
24
+ convertStringsToObjectIds: typeof convertStringsToObjectIds;
25
+ convertObjectIdsToStrings: typeof convertObjectIdsToStrings;
26
+ };
27
+ export {};
@@ -0,0 +1,273 @@
1
+ import { ObjectId } from 'mongodb';
2
+ import _ from 'lodash';
3
+ import { entityUtils } from '@loomcore/common/utils';
4
+ import { stringUtils } from './string.utils.js';
5
+ export const PROPERTIES_THAT_ARE_NOT_OBJECT_IDS = ['_orgId'];
6
+ function convertObjectIdsToStrings(entity, schema) {
7
+ if (!entity)
8
+ return entity;
9
+ const clone = _.cloneDeep(entity);
10
+ if (!schema) {
11
+ if (clone._id && clone._id instanceof ObjectId) {
12
+ clone._id = clone._id.toString();
13
+ }
14
+ return clone;
15
+ }
16
+ const processEntity = (obj, subSchema, path = []) => {
17
+ if (!obj || typeof obj !== 'object')
18
+ return;
19
+ if (subSchema.allOf && Array.isArray(subSchema.allOf)) {
20
+ for (const nestedSchema of subSchema.allOf) {
21
+ processEntity(obj, nestedSchema, path);
22
+ }
23
+ return;
24
+ }
25
+ if (subSchema.type === 'object' && subSchema.properties) {
26
+ for (const [key, propSchema] of Object.entries(subSchema.properties)) {
27
+ if (!propSchema || typeof propSchema !== 'object')
28
+ continue;
29
+ const typedPropSchema = propSchema;
30
+ const fullPath = [...path, key];
31
+ if (typedPropSchema.format === 'objectid') {
32
+ if (path.length === 0 && PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(key)) {
33
+ continue;
34
+ }
35
+ if (obj[key] instanceof ObjectId) {
36
+ obj[key] = obj[key].toString();
37
+ }
38
+ }
39
+ else if (typedPropSchema.type === 'object' && obj[key]) {
40
+ processEntity(obj[key], typedPropSchema, fullPath);
41
+ }
42
+ else if (typedPropSchema.type === 'array' && Array.isArray(obj[key])) {
43
+ const items = typedPropSchema.items;
44
+ if (items) {
45
+ for (let i = 0; i < obj[key].length; i++) {
46
+ if (items.format === 'objectid') {
47
+ if (path.length === 0 && PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(key)) {
48
+ continue;
49
+ }
50
+ if (obj[key][i] instanceof ObjectId) {
51
+ obj[key][i] = obj[key][i].toString();
52
+ }
53
+ }
54
+ else if (items.type === 'object') {
55
+ processEntity(obj[key][i], items, [...fullPath, i.toString()]);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ };
63
+ processEntity(clone, schema);
64
+ return clone;
65
+ }
66
+ function convertStringsToObjectIds(entity, schema) {
67
+ if (!entity)
68
+ return entity;
69
+ const clone = _.cloneDeep(entity);
70
+ const processEntity = (obj, subSchema, path = []) => {
71
+ if (!obj || typeof obj !== 'object')
72
+ return obj;
73
+ if (obj instanceof Date) {
74
+ return obj;
75
+ }
76
+ if (subSchema.allOf && Array.isArray(subSchema.allOf)) {
77
+ let result = { ...obj };
78
+ for (const nestedSchema of subSchema.allOf) {
79
+ result = processEntity(result, nestedSchema, path);
80
+ }
81
+ return result;
82
+ }
83
+ if (Array.isArray(obj)) {
84
+ const items = subSchema.items;
85
+ if (!items)
86
+ return obj;
87
+ if (items.format === 'objectid') {
88
+ if (path.length === 1 && PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(path[0])) {
89
+ return obj;
90
+ }
91
+ return obj.map(item => {
92
+ if (typeof item === 'string' && entityUtils.isValidObjectId(item)) {
93
+ return new ObjectId(item);
94
+ }
95
+ return item;
96
+ });
97
+ }
98
+ if (items.type === 'object') {
99
+ return obj.map((item, index) => processEntity(item, items, [...path, index.toString()]));
100
+ }
101
+ return obj;
102
+ }
103
+ const result = { ...obj };
104
+ if (subSchema.type === 'object' && subSchema.properties) {
105
+ for (const [key, propSchema] of Object.entries(subSchema.properties)) {
106
+ if (!propSchema || typeof propSchema !== 'object' || result[key] === null || result[key] === undefined) {
107
+ continue;
108
+ }
109
+ const typedPropSchema = propSchema;
110
+ const fullPath = [...path, key];
111
+ const value = result[key];
112
+ const isObjectIdField = typedPropSchema.format === 'objectid';
113
+ if (isObjectIdField && path.length === 0 && PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(key)) {
114
+ continue;
115
+ }
116
+ if (isObjectIdField && typeof value === 'string' && entityUtils.isValidObjectId(value)) {
117
+ result[key] = new ObjectId(value);
118
+ }
119
+ else if (typedPropSchema.type === 'array' && Array.isArray(value)) {
120
+ result[key] = processEntity(value, typedPropSchema, fullPath);
121
+ }
122
+ else if (typedPropSchema.type === 'object' && typeof value === 'object' && !Array.isArray(value)) {
123
+ result[key] = processEntity(value, typedPropSchema, fullPath);
124
+ }
125
+ }
126
+ }
127
+ return result;
128
+ };
129
+ return processEntity(clone, schema);
130
+ }
131
+ function convertStringToObjectId(value) {
132
+ if (value instanceof ObjectId) {
133
+ return value;
134
+ }
135
+ if (value === null || value === undefined) {
136
+ return value;
137
+ }
138
+ if (typeof value === 'string' && entityUtils.isValidObjectId(value)) {
139
+ try {
140
+ return new ObjectId(value);
141
+ }
142
+ catch (error) {
143
+ console.warn(`Failed to convert string "${value}" to ObjectId:`, error);
144
+ return value;
145
+ }
146
+ }
147
+ return value;
148
+ }
149
+ function buildMongoMatchFromQueryOptions(queryOptions) {
150
+ const filters = queryOptions.filters || {};
151
+ let match = {};
152
+ for (const [key, value] of Object.entries(filters)) {
153
+ if (value) {
154
+ if (value.eq !== undefined) {
155
+ if (typeof value.eq === 'string' && !PROPERTIES_THAT_ARE_NOT_OBJECT_IDS.includes(key)
156
+ && key.endsWith('Id') && entityUtils.isValidObjectId(value.eq)) {
157
+ match[key] = new ObjectId(value.eq);
158
+ }
159
+ else {
160
+ match[key] = value.eq;
161
+ }
162
+ }
163
+ else if (value.gte !== undefined) {
164
+ match[key] = { $gte: value.gte };
165
+ }
166
+ else if (value.lte !== undefined) {
167
+ match[key] = { $lte: value.lte };
168
+ }
169
+ else if (value.gt !== undefined) {
170
+ match[key] = { $gt: value.gt };
171
+ }
172
+ else if (value.lt !== undefined) {
173
+ match[key] = { $lt: value.lt };
174
+ }
175
+ else if (value.contains !== undefined) {
176
+ match[key] = { $regex: value.contains, $options: 'i' };
177
+ }
178
+ }
179
+ }
180
+ return { $match: match };
181
+ }
182
+ function buildSQLWhereClauseFromQueryOptions(queryOptions, columnAliasMap) {
183
+ const filters = queryOptions.filters || {};
184
+ let whereClause = '';
185
+ for (const [key, value] of Object.entries(filters)) {
186
+ if (value) {
187
+ const tableAlias = (columnAliasMap && columnAliasMap[key]) || '';
188
+ whereClause = addKeyValueToWhereClause(whereClause, key, value, tableAlias);
189
+ }
190
+ }
191
+ return whereClause;
192
+ }
193
+ function addKeyValueToWhereClause(whereClause, key, value, tableAlias = '') {
194
+ let column = tableAlias ? `${tableAlias}.${stringUtils.pascalCase(key)}` : stringUtils.pascalCase(key);
195
+ let formattedValue = '';
196
+ let operator = '=';
197
+ if (value) {
198
+ if (value.eq !== undefined) {
199
+ formattedValue = formatValue(value.eq);
200
+ operator = '=';
201
+ }
202
+ else if (value.gte !== undefined) {
203
+ formattedValue = formatValue(value.gte);
204
+ operator = '>=';
205
+ }
206
+ else if (value.lte !== undefined) {
207
+ formattedValue = formatValue(value.lte);
208
+ operator = '<=';
209
+ }
210
+ else if (value.gt !== undefined) {
211
+ formattedValue = formatValue(value.gt);
212
+ operator = '>';
213
+ }
214
+ else if (value.lt !== undefined) {
215
+ formattedValue = formatValue(value.lt);
216
+ operator = '<';
217
+ }
218
+ else if (value.contains !== undefined) {
219
+ column = `LOWER(${column})`;
220
+ formattedValue = formatValue(value.contains, true).toLowerCase();
221
+ operator = 'LIKE';
222
+ }
223
+ }
224
+ const condition = `${column} ${operator} ${formattedValue}`;
225
+ return appendToWhereClause(whereClause, condition);
226
+ }
227
+ function appendToWhereClause(whereClause, condition) {
228
+ let newWhereClause = whereClause.trim();
229
+ if (newWhereClause.toUpperCase() === 'WHERE' || newWhereClause === '') {
230
+ newWhereClause = `WHERE ${condition}`;
231
+ }
232
+ else {
233
+ newWhereClause = `${newWhereClause} AND ${condition}`;
234
+ }
235
+ return newWhereClause;
236
+ }
237
+ function formatValue(value, isLikeOperator = false) {
238
+ if (typeof value === 'string') {
239
+ if (!isNaN(Number(value))) {
240
+ return value;
241
+ }
242
+ if (value.toLowerCase() === 'true') {
243
+ return 'TRUE';
244
+ }
245
+ if (value.toLowerCase() === 'false') {
246
+ return 'FALSE';
247
+ }
248
+ return isLikeOperator ? `'%${value}%'` : `'${value}'`;
249
+ }
250
+ else if (typeof value === 'number') {
251
+ return value.toString();
252
+ }
253
+ else if (typeof value === 'boolean') {
254
+ return value ? 'TRUE' : 'FALSE';
255
+ }
256
+ else if (value instanceof Date) {
257
+ const dateString = value.toISOString().split('T')[0];
258
+ return `DATETIME('${dateString}')`;
259
+ }
260
+ else {
261
+ throw new Error('Unsupported value type');
262
+ }
263
+ }
264
+ export const dbUtils = {
265
+ buildMongoMatchFromQueryOptions,
266
+ buildSQLWhereClauseFromQueryOptions,
267
+ addKeyValueToWhereClause,
268
+ appendToWhereClause,
269
+ formatValue,
270
+ convertStringToObjectId,
271
+ convertStringsToObjectIds,
272
+ convertObjectIdsToStrings,
273
+ };
@@ -0,0 +1,6 @@
1
+ export * from './address.utils.js';
2
+ export * from './api.utils.js';
3
+ export * from './conversion.utils.js';
4
+ export * from './db.utils.js';
5
+ export * from './password.utils.js';
6
+ export * from './string.utils.js';
@@ -0,0 +1,6 @@
1
+ export * from './address.utils.js';
2
+ export * from './api.utils.js';
3
+ export * from './conversion.utils.js';
4
+ export * from './db.utils.js';
5
+ export * from './password.utils.js';
6
+ export * from './string.utils.js';
@@ -0,0 +1,7 @@
1
+ declare function hashPassword(password: string): Promise<string>;
2
+ declare function comparePasswords(storedPassword: string, suppliedPassword: string): Promise<boolean>;
3
+ export declare const passwordUtils: {
4
+ hashPassword: typeof hashPassword;
5
+ comparePasswords: typeof comparePasswords;
6
+ };
7
+ export {};
@@ -0,0 +1,23 @@
1
+ import { scrypt, randomBytes } from 'node:crypto';
2
+ import { promisify } from 'util';
3
+ const scryptAsync = promisify(scrypt);
4
+ async function hashPassword(password) {
5
+ const salt = randomBytes(8).toString('hex');
6
+ const buf = (await scryptAsync(password, salt, 64));
7
+ return `${buf.toString('hex')}.${salt}`;
8
+ }
9
+ async function comparePasswords(storedPassword, suppliedPassword) {
10
+ if (!storedPassword || !storedPassword.includes('.')) {
11
+ return false;
12
+ }
13
+ const [hashedPassword, salt] = storedPassword.split('.');
14
+ if (!salt) {
15
+ return false;
16
+ }
17
+ const buf = (await scryptAsync(suppliedPassword, salt, 64));
18
+ return buf.toString('hex') === hashedPassword;
19
+ }
20
+ export const passwordUtils = {
21
+ hashPassword,
22
+ comparePasswords
23
+ };
@@ -0,0 +1,9 @@
1
+ declare function camelCase(str: string): string;
2
+ declare function pascalCase(str: string): string;
3
+ declare function kebabCase(str: string): string;
4
+ export declare const stringUtils: {
5
+ camelCase: typeof camelCase;
6
+ pascalCase: typeof pascalCase;
7
+ kebabCase: typeof kebabCase;
8
+ };
9
+ export {};
@@ -0,0 +1,30 @@
1
+ function camelCase(str) {
2
+ return str.split(/[\s-_]+|(?=[A-Z])/)
3
+ .map((word, index) => {
4
+ if (index === 0) {
5
+ return word.charAt(0).toLowerCase() + word.slice(1);
6
+ }
7
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
8
+ })
9
+ .join('');
10
+ }
11
+ function pascalCase(str) {
12
+ return str.split(/[-_]/)
13
+ .map(word => {
14
+ return word.charAt(0).toUpperCase() + word.slice(1);
15
+ })
16
+ .join('');
17
+ }
18
+ function kebabCase(str) {
19
+ let result = str.replace(/([a-z])([A-Z])/g, '$1-$2');
20
+ result = result.toLowerCase()
21
+ .replace(/[ _]/g, '-');
22
+ result = result.replace(/-{2,}/g, '-')
23
+ .replace(/^-|-$/g, '');
24
+ return result;
25
+ }
26
+ export const stringUtils = {
27
+ camelCase,
28
+ pascalCase,
29
+ kebabCase
30
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@loomcore/api",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb",
6
+ "scripts": {
7
+ "clean": "rm -rf dist",
8
+ "tsc": "tsc --project tsconfig.prod.json",
9
+ "build": "npm-run-all -s clean tsc",
10
+ "add": "git add .",
11
+ "commit": "git commit -m \"Updates\"",
12
+ "patch": "npm version patch",
13
+ "push": "git push",
14
+ "publishMe": "npm publish --access public",
15
+ "pub": "npm-run-all -s add commit patch build push publishMe",
16
+ "update-lib-versions": "npx --yes npm-check-updates -u -f @loomcore/models",
17
+ "install-updated-libs": "npm i @loomcore/models",
18
+ "update-libs": "npm-run-all -s update-lib-versions install-updated-libs",
19
+
20
+ "typecheck": "tsc",
21
+ "test": "cross-env NODE_ENV=test vitest run",
22
+ "test:ci": "cross-env NODE_ENV=test vitest run --reporter=json --outputFile=test-results.json",
23
+ "test:watch": "cross-env NODE_ENV=test vitest",
24
+ "coverage": "cross-env NODE_ENV=test vitest run --coverage"
25
+ },
26
+ "author": "Tim Hardy",
27
+ "license": "Apache 2.0",
28
+ "main": "dist/index.js",
29
+ "type": "module",
30
+ "types": "dist/index.d.ts",
31
+ "files": [
32
+ "dist/**/*"
33
+ ],
34
+ "exports": {
35
+ "./config": "./dist/config/index.js",
36
+ "./controllers": "./dist/controllers/index.js",
37
+ "./errors": "./dist/errors/index.js",
38
+ "./middleware": "./dist/middleware/index.js",
39
+ "./models": "./dist/models/index.js",
40
+ "./services": "./dist/services/index.js",
41
+ "./utils": "./dist/utils/index.js"
42
+ },
43
+ "dependencies": {
44
+ "@sendgrid/mail": "^8.1.4",
45
+ "jsonwebtoken": "^9.0.2"
46
+ },
47
+ "peerDependencies": {
48
+ "@loomcore/common": "^0.0.6",
49
+ "@sinclair/typebox": "^0.34.31",
50
+ "cookie-parser": "^1.4.6",
51
+ "express": "^5.1.0",
52
+ "lodash": "^4.17.21",
53
+ "moment": "^2.30.1",
54
+ "mongodb": "^6.16.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/cookie-parser": "^1.4.7",
58
+ "@types/express": "^5.0.1",
59
+ "@types/jsonwebtoken": "^9.0.9",
60
+ "@types/lodash": "^4.17.13",
61
+ "@types/supertest": "^6.0.3",
62
+ "@vitest/coverage-v8": "^3.0.9",
63
+ "cross-env": "^7.0.3",
64
+ "mongodb-memory-server": "^9.3.0",
65
+ "npm-run-all": "^4.1.5",
66
+ "supertest": "^7.1.0",
67
+ "typescript": "^5.8.3",
68
+ "vite": "^6.2.5",
69
+ "vitest": "^3.0.9"
70
+ }
71
+ }