@sqrzro/server 2.0.0-bz.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright 2023 Richard Carter
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ Square Zero Components
@@ -0,0 +1,10 @@
1
+ import type { NextRequest } from 'next/server';
2
+ import { NextResponse } from 'next/server';
3
+ import type { Errorable, UserObject } from './interfaces';
4
+ export declare function handleLogin(formData: FormData, lookupFn: (email: string) => Promise<UserObject | null>): Promise<Errorable<UserObject>>;
5
+ interface MeObject {
6
+ name: string;
7
+ email: string;
8
+ }
9
+ export declare function handleMe(request: NextRequest, lookupFn: (id: number) => Promise<MeObject | null>): Promise<NextResponse>;
10
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleMe = exports.handleLogin = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const server_1 = require("next/server");
6
+ const DataService_1 = require("./DataService");
7
+ const PasswordService_1 = require("./PasswordService");
8
+ const SessionService_1 = require("./SessionService");
9
+ const LoginRequest_1 = tslib_1.__importDefault(require("./LoginRequest"));
10
+ async function handleLogin(formData, lookupFn) {
11
+ async function mutateFn(data) {
12
+ const user = await lookupFn(data.email);
13
+ const isUserValid = await (0, PasswordService_1.verifyPassword)(data.password, user?.password);
14
+ return user && isUserValid ? user : null;
15
+ }
16
+ const response = await (0, DataService_1.mutate)({
17
+ fn: mutateFn,
18
+ formData,
19
+ request: LoginRequest_1.default,
20
+ onSuccess: async (user) => {
21
+ await (0, SessionService_1.createSessionForId)(user.id);
22
+ },
23
+ });
24
+ return response;
25
+ }
26
+ exports.handleLogin = handleLogin;
27
+ async function handleMe(request, lookupFn) {
28
+ const token = new URL(request.url).searchParams.get('token');
29
+ const userId = await (0, SessionService_1.getIdFromToken)(token);
30
+ const me = await lookupFn(userId);
31
+ if (!me) {
32
+ return server_1.NextResponse.json({ valid: false }, { status: 401 });
33
+ }
34
+ return server_1.NextResponse.json({ name: me.name, email: me.email, valid: true });
35
+ }
36
+ exports.handleMe = handleMe;
@@ -0,0 +1,29 @@
1
+ import type Joi from 'joi';
2
+ import type { Errorable } from './interfaces';
3
+ interface MutateArgs<F extends object> {
4
+ formData: FormData;
5
+ onSuccess?: (model: F) => Promise<void> | void;
6
+ onValidationError?: (errors: Record<string, string>) => void;
7
+ request: Promise<Joi.ObjectSchema<F>>;
8
+ }
9
+ interface MutateArgsWithFn<F extends object, M> extends Omit<MutateArgs<F>, 'onSuccess'> {
10
+ fn: (data: F) => Promise<M | null>;
11
+ onSuccess?: (model: M) => Promise<void> | void;
12
+ }
13
+ export declare function mutate<F extends object>(args: MutateArgs<F>): Promise<Errorable<F>>;
14
+ export declare function mutate<F extends object, M>(args: MutateArgsWithFn<F, M>): Promise<Errorable<M>>;
15
+ interface PaginationArgs {
16
+ skip: number;
17
+ take: number;
18
+ }
19
+ interface FindManyArgs extends PaginationArgs {
20
+ orderBy: Record<string, string>;
21
+ where: Record<string, string>;
22
+ }
23
+ interface GetListArgs<T> {
24
+ allowedFilters?: string[];
25
+ fn: (args: FindManyArgs) => Promise<T[]>;
26
+ searchParams?: Record<string, string>;
27
+ }
28
+ export declare function getList<T>({ allowedFilters, fn, searchParams, }: GetListArgs<T>): Promise<Errorable<T[]>>;
29
+ export {};
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getList = exports.mutate = void 0;
4
+ const utility_1 = require("@sqrzro/utility");
5
+ const RequestService_1 = require("./RequestService");
6
+ function hasFn(args) {
7
+ return Boolean(Object.prototype.hasOwnProperty.call(args, 'fn'));
8
+ }
9
+ async function mutate(args) {
10
+ const [validated, validationErrors] = await (0, RequestService_1.validateSchema)(args.formData, await args.request);
11
+ if (validationErrors) {
12
+ args.onValidationError?.(validationErrors);
13
+ return [null, validationErrors];
14
+ }
15
+ if (!hasFn(args)) {
16
+ try {
17
+ await args.onSuccess?.(validated);
18
+ }
19
+ catch (err) {
20
+ return [
21
+ null,
22
+ { $root: err instanceof Error ? `SERVER: ${err.message}` : 'SERVER_ERROR' },
23
+ ];
24
+ }
25
+ return [validated, null];
26
+ }
27
+ const model = await args.fn(validated);
28
+ if (!model) {
29
+ return [null, { $root: 'SERVER_ERROR' }];
30
+ }
31
+ try {
32
+ await args.onSuccess?.(model);
33
+ }
34
+ catch (err) {
35
+ return [null, { $root: err instanceof Error ? `SERVER: ${err.message}` : 'SERVER_ERROR' }];
36
+ }
37
+ return [model, null];
38
+ }
39
+ exports.mutate = mutate;
40
+ const DEFAULT_SORT = 'id';
41
+ const DEFAULT_DIR = 'asc';
42
+ const DEFAULT_PAGE = 1;
43
+ const DEFAULT_LIMIT = 10;
44
+ function createPaginationArgs(page = DEFAULT_PAGE) {
45
+ return { skip: (page - 1) * DEFAULT_LIMIT, take: DEFAULT_LIMIT };
46
+ }
47
+ function createFindManyArgs(searchParams, allowedFilters = []) {
48
+ const { dir, page, sort, ...filters } = searchParams || {};
49
+ return {
50
+ ...createPaginationArgs(page ? parseInt(page, 10) : DEFAULT_PAGE),
51
+ orderBy: { [sort || DEFAULT_SORT]: dir || DEFAULT_DIR },
52
+ where: (0, utility_1.getFromObject)(filters, allowedFilters),
53
+ };
54
+ }
55
+ async function getList({ allowedFilters, fn, searchParams, }) {
56
+ try {
57
+ const args = createFindManyArgs(searchParams, allowedFilters);
58
+ return [await fn(args), null];
59
+ }
60
+ catch (err) {
61
+ return [null, { message: err instanceof Error ? err.message : 'SERVER_ERROR' }];
62
+ }
63
+ }
64
+ exports.getList = getList;
@@ -0,0 +1,4 @@
1
+ import Joi from 'joi';
2
+ import type { LoginFormFields } from './interfaces';
3
+ declare const LoginRequest: Promise<Joi.ObjectSchema<LoginFormFields>>;
4
+ export default LoginRequest;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-magic-numbers */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const tslib_1 = require("tslib");
5
+ const joi_1 = tslib_1.__importDefault(require("joi"));
6
+ const RequestService_1 = require("./RequestService");
7
+ const LoginRequest = (0, RequestService_1.createSchema)({
8
+ email: joi_1.default.string().email({ minDomainSegments: 2, tlds: false }).required(),
9
+ password: joi_1.default.string().min(8).required(),
10
+ });
11
+ exports.default = LoginRequest;
@@ -0,0 +1,6 @@
1
+ type PasswordRule = 'lower' | 'min' | 'number' | 'symbol' | 'upper';
2
+ export declare function createSecret(): Promise<string>;
3
+ export declare function hashPassword(password: string): Promise<string>;
4
+ export declare function verifyPassword(data?: string, encrypted?: string): Promise<boolean>;
5
+ export declare function checkPasswordComplexity(password: string): Promise<PasswordRule[]>;
6
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkPasswordComplexity = exports.verifyPassword = exports.hashPassword = exports.createSecret = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const bcryptjs_1 = tslib_1.__importDefault(require("bcryptjs"));
6
+ const PW_SALT_ROUNDS = 12;
7
+ const PASSWORD_RULES = {
8
+ min: 8,
9
+ upper: 1,
10
+ lower: 1,
11
+ number: 1,
12
+ symbol: 1,
13
+ };
14
+ function checkPasswordMin(password, value) {
15
+ return password.length >= value;
16
+ }
17
+ function checkPasswordUpper(password, value) {
18
+ return password.replace(/[^A-Z]/gu, '').length >= value;
19
+ }
20
+ function checkPasswordLower(password, value) {
21
+ return password.replace(/[^a-z]/gu, '').length >= value;
22
+ }
23
+ function checkPasswordNumber(password, value) {
24
+ return password.replace(/[^0-9]/gu, '').length >= value;
25
+ }
26
+ function checkPasswordSymbol(password, value) {
27
+ return password.replace(/[^$]/gu, '').length >= value;
28
+ }
29
+ const PASSWORD_FUNCTIONS = {
30
+ min: checkPasswordMin,
31
+ upper: checkPasswordUpper,
32
+ lower: checkPasswordLower,
33
+ number: checkPasswordNumber,
34
+ symbol: checkPasswordSymbol,
35
+ };
36
+ async function createSecret() {
37
+ const secret = await bcryptjs_1.default.genSalt(PW_SALT_ROUNDS);
38
+ return secret;
39
+ }
40
+ exports.createSecret = createSecret;
41
+ async function hashPassword(password) {
42
+ const hash = await bcryptjs_1.default.hash(password, PW_SALT_ROUNDS);
43
+ return hash;
44
+ }
45
+ exports.hashPassword = hashPassword;
46
+ async function verifyPassword(data, encrypted) {
47
+ if (!data || !encrypted) {
48
+ return false;
49
+ }
50
+ const verified = await bcryptjs_1.default.compare(data, encrypted);
51
+ return verified;
52
+ }
53
+ exports.verifyPassword = verifyPassword;
54
+ async function checkPasswordComplexity(password) {
55
+ const entries = Object.entries(PASSWORD_RULES);
56
+ const rules = entries
57
+ .map(([key, value]) => (PASSWORD_FUNCTIONS[key](password, value) ? key : null))
58
+ .filter(Boolean);
59
+ return new Promise((resolve) => {
60
+ resolve(rules);
61
+ });
62
+ }
63
+ exports.checkPasswordComplexity = checkPasswordComplexity;
@@ -0,0 +1,21 @@
1
+ import Joi from 'joi';
2
+ import type { ErrorablePromise } from './interfaces';
3
+ export declare function validate(): typeof Joi;
4
+ /**
5
+ * This function takes FormData and a schema. It then attempts to transform the FormData into an
6
+ * object that matches `T`. This is because the FormData object is not typed and we want to be able
7
+ * to validate it properly typed.
8
+ *
9
+ * Once transformed, the object is validated against the schema. This will result in either a
10
+ * properly typed `T` object or an array of validation errors.
11
+ * @param formData
12
+ * @param validation
13
+ * @returns
14
+ */
15
+ export declare function validateSchema<T extends object>(formData: FormData, validation: Joi.ObjectSchema<T>): ErrorablePromise<T>;
16
+ export declare function createSchema<T>(schema: Joi.PartialSchemaMap<T>): Promise<Joi.ObjectSchema<T>>;
17
+ interface ExtendSchemaOptions {
18
+ required?: string[];
19
+ }
20
+ export declare function extendSchema<T>(schema: Promise<Joi.ObjectSchema<T>>, appends: Joi.PartialSchemaMap<T>, options?: ExtendSchemaOptions): Promise<Joi.ObjectSchema<T>>;
21
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extendSchema = exports.createSchema = exports.validateSchema = exports.validate = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const joi_1 = tslib_1.__importDefault(require("joi"));
6
+ function validate() {
7
+ return joi_1.default;
8
+ }
9
+ exports.validate = validate;
10
+ function isExternalError(value) {
11
+ return Boolean(typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'code'));
12
+ }
13
+ function checkExternalErrors(validated) {
14
+ const externalErrors = Object.values(validated).filter((value) => isExternalError(value));
15
+ if (externalErrors.length) {
16
+ return [
17
+ null,
18
+ externalErrors.reduce((acc, value) => ({ ...acc, [value.path[0]]: value.code }), {}),
19
+ ];
20
+ }
21
+ return [validated, null];
22
+ }
23
+ function transformErrors(error) {
24
+ return error.details.reduce((acc, cur) => ({ ...acc, [cur.path[0]]: cur.message }), {});
25
+ }
26
+ function transformString(value) {
27
+ return value;
28
+ }
29
+ function transformNumber(value) {
30
+ return parseInt(value, 10);
31
+ }
32
+ function transformBoolean(value) {
33
+ return value === 'true' || value === '1';
34
+ }
35
+ function transformDate(value) {
36
+ if (!value) {
37
+ return null;
38
+ }
39
+ return new Date(value);
40
+ }
41
+ const transformMap = {
42
+ string: transformString,
43
+ number: transformNumber,
44
+ boolean: transformBoolean,
45
+ date: transformDate,
46
+ };
47
+ function transformValue(value, type) {
48
+ return transformMap[type](String(value));
49
+ }
50
+ /**
51
+ * Take the formData and the validation schema and transform the formData into an object that
52
+ * contains the correct types, as defined by the validation schema. This is because the FormData
53
+ * object will only contain strings (or File/Blob objects) and we want to be able to use the object
54
+ * correctly typed. For example, if the validation schema defines a property as a boolean, we want
55
+ * the value to be either true or false, not the string "true" or "false".
56
+ *
57
+ * @param formData
58
+ * @param validation
59
+ */
60
+ function transform(formData, validation) {
61
+ const keys = validation.describe().keys;
62
+ const entries = Object.entries(Object.fromEntries(formData.entries()));
63
+ const result = {};
64
+ entries.forEach(([key, value]) => {
65
+ const entry = keys[key];
66
+ if (entry) {
67
+ result[key] = transformValue(value, entry.type);
68
+ }
69
+ else {
70
+ result[key] = value;
71
+ }
72
+ });
73
+ return result;
74
+ }
75
+ /**
76
+ * This function takes FormData and a schema. It then attempts to transform the FormData into an
77
+ * object that matches `T`. This is because the FormData object is not typed and we want to be able
78
+ * to validate it properly typed.
79
+ *
80
+ * Once transformed, the object is validated against the schema. This will result in either a
81
+ * properly typed `T` object or an array of validation errors.
82
+ * @param formData
83
+ * @param validation
84
+ * @returns
85
+ */
86
+ async function validateSchema(formData, validation) {
87
+ try {
88
+ const transformed = transform(formData, validation);
89
+ const validated = await validation.validateAsync(transformed, {
90
+ abortEarly: false,
91
+ });
92
+ return checkExternalErrors(validated);
93
+ }
94
+ catch (err) {
95
+ if (err instanceof joi_1.default.ValidationError) {
96
+ return [null, transformErrors(err)];
97
+ }
98
+ if (err instanceof Error) {
99
+ return [null, { $root: `Validation error occured: ${err.message}` }];
100
+ }
101
+ return [null, { $root: 'Unknown validation error occured' }];
102
+ }
103
+ }
104
+ exports.validateSchema = validateSchema;
105
+ async function createSchema(schema) {
106
+ return new Promise((resolve) => {
107
+ resolve(joi_1.default.object(schema));
108
+ });
109
+ }
110
+ exports.createSchema = createSchema;
111
+ async function extendSchema(schema, appends, options) {
112
+ const extended = (await schema).concat(joi_1.default.object(appends));
113
+ const required = (options?.required || []).reduce((acc, cur) => ({
114
+ ...acc,
115
+ [cur]: extended.extract(cur).required(),
116
+ }), {});
117
+ return new Promise((resolve) => {
118
+ resolve(extended.concat(joi_1.default.object(required)));
119
+ });
120
+ }
121
+ exports.extendSchema = extendSchema;
@@ -0,0 +1,5 @@
1
+ export declare function createSessionForId(id: number): Promise<void>;
2
+ export declare function getIdFromToken(token: string | null): Promise<number>;
3
+ export declare function getIdFromSession(): Promise<number>;
4
+ export declare function checkSessionExists(): Promise<boolean>;
5
+ export declare function removeSession(): Promise<void>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeSession = exports.checkSessionExists = exports.getIdFromSession = exports.getIdFromToken = exports.createSessionForId = void 0;
4
+ const jose_1 = require("jose");
5
+ const uuid_1 = require("uuid");
6
+ const headers_1 = require("next/headers");
7
+ const SESSION_COOKIE_EXPIRY = 7200;
8
+ async function createSessionForId(id) {
9
+ const token = await new jose_1.SignJWT({})
10
+ .setExpirationTime('1w')
11
+ .setIssuedAt()
12
+ .setJti((0, uuid_1.v4)())
13
+ .setProtectedHeader({ alg: 'HS256' })
14
+ .setSubject(id.toString())
15
+ .sign(new TextEncoder().encode('secret'));
16
+ (0, headers_1.cookies)().set('token', token, {
17
+ httpOnly: true,
18
+ maxAge: SESSION_COOKIE_EXPIRY,
19
+ });
20
+ }
21
+ exports.createSessionForId = createSessionForId;
22
+ async function getIdFromToken(token) {
23
+ if (!token) {
24
+ throw new Error('Could not get ID. Token does not exist.');
25
+ }
26
+ try {
27
+ const verified = await (0, jose_1.jwtVerify)(token, new TextEncoder().encode('secret'));
28
+ return verified.payload.sub ? parseInt(verified.payload.sub, 10) : 0;
29
+ }
30
+ catch (err) {
31
+ throw new Error('Could not get ID. Token is invalid.');
32
+ }
33
+ }
34
+ exports.getIdFromToken = getIdFromToken;
35
+ async function getIdFromSession() {
36
+ const userId = await getIdFromToken((0, headers_1.cookies)().get('token')?.value || null);
37
+ return userId;
38
+ }
39
+ exports.getIdFromSession = getIdFromSession;
40
+ async function checkSessionExists() {
41
+ try {
42
+ await getIdFromSession();
43
+ return true;
44
+ }
45
+ catch (err) {
46
+ return false;
47
+ }
48
+ }
49
+ exports.checkSessionExists = checkSessionExists;
50
+ async function removeSession() {
51
+ return new Promise((resolve) => {
52
+ (0, headers_1.cookies)().set('token', '', { httpOnly: true, maxAge: 0 });
53
+ resolve();
54
+ });
55
+ }
56
+ exports.removeSession = removeSession;
@@ -0,0 +1,7 @@
1
+ export * from './LoginRequest';
2
+ export * from './AuthService';
3
+ export * from './PasswordService';
4
+ export * from './DataService';
5
+ export * from './RequestService';
6
+ export * from './SessionService';
7
+ export * from './interfaces';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./LoginRequest"), exports);
5
+ tslib_1.__exportStar(require("./AuthService"), exports);
6
+ tslib_1.__exportStar(require("./PasswordService"), exports);
7
+ tslib_1.__exportStar(require("./DataService"), exports);
8
+ tslib_1.__exportStar(require("./RequestService"), exports);
9
+ tslib_1.__exportStar(require("./SessionService"), exports);
10
+ tslib_1.__exportStar(require("./interfaces"), exports);
@@ -0,0 +1,11 @@
1
+ export type CastType = 'date';
2
+ export type CastObject<T extends object> = Partial<Record<keyof T, CastType>>;
3
+ export type Errorable<T> = [null, Record<string, string>] | [T, null];
4
+ export type ErrorablePromise<T> = Promise<Errorable<T>>;
5
+ export interface LoginFormFields {
6
+ email: string;
7
+ password: string;
8
+ }
9
+ export interface UserObject extends LoginFormFields {
10
+ id: number;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { NextRequest } from 'next/server';
3
+ export declare function runMiddleware(request: NextRequest): Promise<NextResponse>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runMiddleware = void 0;
4
+ const server_1 = require("next/server");
5
+ async function getUrl(url) {
6
+ return new Promise((resolve) => {
7
+ resolve(`http://localhost:8080/${url}`);
8
+ });
9
+ }
10
+ async function redirect(request, pathname) {
11
+ if (new URL(request.url).pathname !== `/${pathname}`) {
12
+ return server_1.NextResponse.redirect(await getUrl(pathname));
13
+ }
14
+ return server_1.NextResponse.next();
15
+ }
16
+ async function getMe(token) {
17
+ if (!token) {
18
+ return null;
19
+ }
20
+ try {
21
+ const response = await fetch(await getUrl(`/api/me?token=${token}`));
22
+ return (await response.json());
23
+ }
24
+ catch (err) {
25
+ return null;
26
+ }
27
+ }
28
+ async function runMiddleware(request) {
29
+ const me = await getMe(request.cookies.get('token')?.value);
30
+ if (!me?.valid) {
31
+ return redirect(request, 'auth/login');
32
+ }
33
+ return server_1.NextResponse.next();
34
+ }
35
+ exports.runMiddleware = runMiddleware;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@sqrzro/server",
3
+ "version": "2.0.0-bz.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./middleware": "./dist/middleware.js"
9
+ },
10
+ "license": "ISC",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "dependencies": {
15
+ "@sqrzro/utility": "bz",
16
+ "bcryptjs": "^2.4.3",
17
+ "joi": "^17.11.0",
18
+ "jose": "^5.0.1",
19
+ "next": "^14.0.0",
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
+ "uuid": "^9.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@sqrzro/eslint-plugin": "bz",
26
+ "@sqrzro/prettier-config": "bz",
27
+ "@types/bcryptjs": "^2.4.6",
28
+ "@types/react": "^18.0.28",
29
+ "@types/uuid": "^9.0.7",
30
+ "esbuild": "^0.19.5",
31
+ "eslint": "^8.55.0",
32
+ "prettier": "^2.8.4",
33
+ "rimraf": "^4.1.2",
34
+ "typescript": "^5.2.2"
35
+ },
36
+ "scripts": {
37
+ "build": "yarn clean && tsc --project tsconfig.build.json",
38
+ "clean": "rimraf ./dist",
39
+ "dev": "tsc -p tsconfig.build.json -w",
40
+ "lint": "tsc --noEmit && eslint \"./src/**/*.ts\"",
41
+ "prepare": "yarn build",
42
+ "prettier": "prettier --write \"./src/**/*.ts\"",
43
+ "start": "yarn dev"
44
+ }
45
+ }