unconventional 0.0.1 → 0.0.3
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/README.md +2 -1
- package/dist/core/abstractbase.controller.d.ts +35 -0
- package/dist/core/abstractbase.controller.js +198 -0
- package/dist/core/api-error.d.ts +28 -0
- package/dist/core/api-error.js +78 -0
- package/dist/core/base.cache.d.ts +15 -0
- package/dist/core/base.cache.js +72 -0
- package/dist/core/base.model.d.ts +39 -0
- package/dist/core/base.model.js +189 -0
- package/dist/core/base.options.d.ts +56 -0
- package/dist/core/base.options.js +3 -0
- package/dist/core/base.service.d.ts +15 -0
- package/dist/core/base.service.js +122 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/pg.driver.d.ts +2 -0
- package/dist/core/pg.driver.js +21 -0
- package/dist/decorators/index.d.ts +4 -0
- package/dist/decorators/index.decorator.d.ts +4 -0
- package/dist/decorators/index.decorator.js +6 -0
- package/dist/decorators/index.js +4 -0
- package/dist/decorators/prop.decorator.d.ts +4 -0
- package/dist/decorators/prop.decorator.js +13 -0
- package/dist/decorators/timestamp.decorator.d.ts +4 -0
- package/dist/decorators/timestamp.decorator.js +6 -0
- package/dist/decorators/utils.d.ts +1 -0
- package/dist/decorators/utils.js +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +32 -0
- package/dist/types/api.types.d.ts +30 -0
- package/dist/types/api.types.js +17 -0
- package/dist/types/db.types.d.ts +24 -0
- package/dist/types/db.types.js +12 -0
- package/dist/types/decorator.types.d.ts +38 -0
- package/dist/types/decorator.types.js +7 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +4 -0
- package/dist/types/server.types.d.ts +10 -0
- package/dist/types/server.types.js +1 -0
- package/dist/utils/api.utils.d.ts +8 -0
- package/dist/utils/api.utils.js +46 -0
- package/dist/utils/array.utils.d.ts +2 -0
- package/dist/utils/array.utils.js +20 -0
- package/dist/utils/cache.utils.d.ts +2 -0
- package/dist/utils/cache.utils.js +14 -0
- package/dist/utils/crypto.utils.d.ts +1 -0
- package/dist/utils/crypto.utils.js +9 -0
- package/dist/utils/db.utils.d.ts +9 -0
- package/dist/utils/db.utils.js +127 -0
- package/dist/utils/function.utils.d.ts +10 -0
- package/dist/utils/function.utils.js +14 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/middleware.utils.d.ts +3 -0
- package/dist/utils/middleware.utils.js +10 -0
- package/dist/utils/server.utils.d.ts +4 -0
- package/dist/utils/server.utils.js +27 -0
- package/package.json +37 -11
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ConflictResolution, Expansion, SqlConflict, SqlOrder, SqlPaginate, SqlWhere, SqlWhereOperator } from "unconventional-pg-queries";
|
|
2
|
+
import BaseModel from "../core/base.model";
|
|
3
|
+
export declare function getExpand<T extends typeof BaseModel>(model: T, expand?: string): Record<string, Expansion>;
|
|
4
|
+
export declare function getWhere<T extends typeof BaseModel>(model: T, filterString?: string): SqlWhere[];
|
|
5
|
+
export declare function getOrder<T extends typeof BaseModel>(model: T, sort?: string): SqlOrder[];
|
|
6
|
+
export declare function getPage<T extends typeof BaseModel>(model: T, cursor?: string | number): SqlPaginate | undefined;
|
|
7
|
+
export declare function getReturn<T extends typeof BaseModel>(model: T): string[];
|
|
8
|
+
export declare function getConflict<T extends typeof BaseModel>(model: T, action: ConflictResolution, constraint?: Array<keyof InstanceType<T>>): SqlConflict | undefined;
|
|
9
|
+
export declare function buildFilter<T>(field: keyof T, operator: SqlWhereOperator, value?: any): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { AndOr, OneOrMany, SqlDirection } from "unconventional-pg-queries";
|
|
2
|
+
import { NullSqlOperators, ValidSortDirections, ValidSqlOperators, andOrPattern } from "../types/db.types";
|
|
3
|
+
import { RelationType } from "../types/decorator.types";
|
|
4
|
+
import { parseExpandString, splitExpandUnit } from "./api.utils";
|
|
5
|
+
import { equalArrays } from "./array.utils";
|
|
6
|
+
import { APIError } from "../core/api-error";
|
|
7
|
+
export function getExpand(model, expand) {
|
|
8
|
+
if (!expand)
|
|
9
|
+
return {};
|
|
10
|
+
const propSummary = model.schema.props;
|
|
11
|
+
const expansions = {};
|
|
12
|
+
const parts = parseExpandString(expand);
|
|
13
|
+
parts.forEach((field) => {
|
|
14
|
+
const { parent, children } = splitExpandUnit(field);
|
|
15
|
+
const relation = propSummary[parent]?.relation;
|
|
16
|
+
if (!relation)
|
|
17
|
+
throw APIError.errInvalidQueryParameter(`Invalid expansion: ${parent}`);
|
|
18
|
+
expansions[parent] = {
|
|
19
|
+
type: [RelationType.HasOne, RelationType.BelongsTo].includes(relation.type) ? OneOrMany.One : OneOrMany.Many,
|
|
20
|
+
fromTable: model.collection,
|
|
21
|
+
fromField: relation.from.toString(),
|
|
22
|
+
toTable: relation.model.collection,
|
|
23
|
+
toField: relation.to.toString(),
|
|
24
|
+
throughTable: relation.through?.model.collection,
|
|
25
|
+
throughFromField: relation.through?.from.toString(),
|
|
26
|
+
throughToField: relation.through?.to.toString(),
|
|
27
|
+
expand: children ? getExpand(relation.model, children) : undefined
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
return expansions;
|
|
31
|
+
}
|
|
32
|
+
export function getWhere(model, filterString) {
|
|
33
|
+
if (!filterString)
|
|
34
|
+
return [];
|
|
35
|
+
const propSummary = model.schema.props;
|
|
36
|
+
const wheres = [];
|
|
37
|
+
if (filterString) {
|
|
38
|
+
filterString.split(andOrPattern).forEach((filter) => {
|
|
39
|
+
let andOr = undefined;
|
|
40
|
+
if (filter.startsWith(AndOr.And)) {
|
|
41
|
+
filter = filter.substring(AndOr.And.length + 1);
|
|
42
|
+
andOr = AndOr.And;
|
|
43
|
+
}
|
|
44
|
+
else if (filter.startsWith(AndOr.Or)) {
|
|
45
|
+
filter = filter.substring(AndOr.Or.length + 1);
|
|
46
|
+
andOr = AndOr.Or;
|
|
47
|
+
}
|
|
48
|
+
let field = filter.substring(0, filter.indexOf(' '));
|
|
49
|
+
let value = filter.substring(filter.lastIndexOf(' ') + 1);
|
|
50
|
+
let operator = filter.substring(filter.indexOf(' ') + 1, filter.lastIndexOf(' '));
|
|
51
|
+
let jsonPath = [];
|
|
52
|
+
if (operator.startsWith('IS')) {
|
|
53
|
+
operator = `${operator} ${value}`;
|
|
54
|
+
value = null;
|
|
55
|
+
}
|
|
56
|
+
if (field.includes('.')) {
|
|
57
|
+
jsonPath = field.split('.');
|
|
58
|
+
field = jsonPath.shift();
|
|
59
|
+
}
|
|
60
|
+
if (!propSummary[field])
|
|
61
|
+
throw APIError.errInvalidQueryParameter(`Invalid filter: '${field}'`);
|
|
62
|
+
if (!ValidSqlOperators.includes(operator))
|
|
63
|
+
throw APIError.errInvalidQueryParameter(`Invalid operator in filter: '${operator}'`);
|
|
64
|
+
if (!value && !NullSqlOperators.includes(operator))
|
|
65
|
+
throw APIError.errInvalidQueryParameter(`Invalid value in filter: '${value}'`);
|
|
66
|
+
wheres.push({ field, jsonPath, operator: operator, value, andOr });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return wheres;
|
|
70
|
+
}
|
|
71
|
+
export function getOrder(model, sort) {
|
|
72
|
+
if (!sort)
|
|
73
|
+
return [{ field: model.idField, direction: SqlDirection.Asc }];
|
|
74
|
+
const propSummary = model.schema.props;
|
|
75
|
+
const orders = [];
|
|
76
|
+
sort.split(',').forEach((item) => {
|
|
77
|
+
let [field, direction] = item.split(' ');
|
|
78
|
+
let jsonPath = [];
|
|
79
|
+
direction ||= SqlDirection.Asc;
|
|
80
|
+
if (field.includes('.')) {
|
|
81
|
+
jsonPath = field.split('.');
|
|
82
|
+
field = jsonPath.shift();
|
|
83
|
+
}
|
|
84
|
+
if (!propSummary[field])
|
|
85
|
+
throw APIError.errInvalidQueryParameter(`Invalid orderBy field: '${field}'`);
|
|
86
|
+
if (!ValidSortDirections.includes(direction.toUpperCase()))
|
|
87
|
+
throw APIError.errInvalidQueryParameter(`Invalid orderBy direction: '${direction}'`);
|
|
88
|
+
orders.push({ field, jsonPath, direction: direction.toUpperCase() });
|
|
89
|
+
});
|
|
90
|
+
return orders;
|
|
91
|
+
}
|
|
92
|
+
export function getPage(model, cursor) {
|
|
93
|
+
if (!cursor)
|
|
94
|
+
return undefined;
|
|
95
|
+
const pagination = { field: model.idField, cursor };
|
|
96
|
+
return pagination;
|
|
97
|
+
}
|
|
98
|
+
export function getReturn(model) {
|
|
99
|
+
const propSummary = model.schema.props;
|
|
100
|
+
const fields = [];
|
|
101
|
+
Object.entries(propSummary).forEach(([field, summary]) => {
|
|
102
|
+
if (!summary.relation)
|
|
103
|
+
fields.push(field);
|
|
104
|
+
});
|
|
105
|
+
return fields;
|
|
106
|
+
}
|
|
107
|
+
export function getConflict(model, action, constraint) {
|
|
108
|
+
if (!constraint)
|
|
109
|
+
return undefined;
|
|
110
|
+
const indexes = model.schema.indexes;
|
|
111
|
+
for (const index of indexes) {
|
|
112
|
+
const keys = Object.keys(index.definition);
|
|
113
|
+
if (equalArrays(keys, constraint))
|
|
114
|
+
return { action, constraint: keys };
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
export function buildFilter(field, operator, value) {
|
|
119
|
+
let formatted = `${value}`;
|
|
120
|
+
if (typeof value === 'string')
|
|
121
|
+
formatted = `'${value}'`;
|
|
122
|
+
else if (Array.isArray(value))
|
|
123
|
+
formatted = (typeof value[0] === 'string') ? `('${value.join("','")}')` : `(${value.join()})`;
|
|
124
|
+
else if (value == null || NullSqlOperators.includes(operator))
|
|
125
|
+
formatted = '';
|
|
126
|
+
return `${field} ${operator} ${formatted}`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { BackendServer } from "../server";
|
|
3
|
+
import { ServerConfig } from "../types";
|
|
4
|
+
interface CFFunctionDefinition {
|
|
5
|
+
serverConfig: ServerConfig;
|
|
6
|
+
routeRegistrationFn: (server: BackendServer) => void;
|
|
7
|
+
}
|
|
8
|
+
export type CFFunctionHandler = (config: CFFunctionDefinition) => PagesFunction<{}>;
|
|
9
|
+
declare const functionHandler: CFFunctionHandler;
|
|
10
|
+
export default functionHandler;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BackendServer } from "../server";
|
|
2
|
+
const functionHandler = (config) => {
|
|
3
|
+
const server = new BackendServer();
|
|
4
|
+
let handler;
|
|
5
|
+
return async (eventContext) => {
|
|
6
|
+
if (!server.configured) {
|
|
7
|
+
server.configure(config.serverConfig);
|
|
8
|
+
config.routeRegistrationFn(server);
|
|
9
|
+
handler = server.start();
|
|
10
|
+
}
|
|
11
|
+
return await handler(eventContext);
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export default functionHandler;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './api.utils';
|
|
2
|
+
export * from './array.utils';
|
|
3
|
+
export * from './cache.utils';
|
|
4
|
+
export * from './crypto.utils';
|
|
5
|
+
export * from './db.utils';
|
|
6
|
+
export * from './function.utils';
|
|
7
|
+
export * from './middleware.utils';
|
|
8
|
+
export * from './server.utils';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './api.utils';
|
|
2
|
+
export * from './array.utils';
|
|
3
|
+
export * from './cache.utils';
|
|
4
|
+
export * from './crypto.utils';
|
|
5
|
+
export * from './db.utils';
|
|
6
|
+
export * from './function.utils';
|
|
7
|
+
export * from './middleware.utils';
|
|
8
|
+
export * from './server.utils';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Input, MiddlewareHandler } from "hono";
|
|
2
|
+
import { BaseHandler, Env } from "../types/api.types";
|
|
3
|
+
export declare function Or<E extends Env = Env, P extends string = any, I extends Input = Input, T extends BaseHandler<E, P, I> | MiddlewareHandler<E, P, I> = MiddlewareHandler<E, P, I>>(...methods: Array<T>): MiddlewareHandler<E, P, I>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function Or(...methods) {
|
|
2
|
+
return async (ctx, next) => {
|
|
3
|
+
const halt = async () => { };
|
|
4
|
+
const promises = methods.map(x => x(ctx, halt));
|
|
5
|
+
const values = await Promise.allSettled(promises);
|
|
6
|
+
if (!values.some(x => x.status === "fulfilled"))
|
|
7
|
+
throw values[0].reason;
|
|
8
|
+
await next();
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DBFactory } from "../types";
|
|
2
|
+
export declare const defaultCors: import("hono").MiddlewareHandler;
|
|
3
|
+
export declare const bindDatabase: (getDB: DBFactory) => (ctx: any, next: any) => Promise<void>;
|
|
4
|
+
export declare const defaultErrorHandler: (err: any, ctx: any) => any;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cors } from "hono/cors";
|
|
2
|
+
import BaseModel from "../core/base.model";
|
|
3
|
+
import { APIError } from "../core";
|
|
4
|
+
export const defaultCors = cors({
|
|
5
|
+
origin: "*",
|
|
6
|
+
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
7
|
+
});
|
|
8
|
+
export const bindDatabase = (getDB) => {
|
|
9
|
+
return async (ctx, next) => {
|
|
10
|
+
if (!BaseModel.db)
|
|
11
|
+
BaseModel.db = getDB(ctx);
|
|
12
|
+
await next();
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export const defaultErrorHandler = (err, ctx) => {
|
|
16
|
+
if (err instanceof APIError) {
|
|
17
|
+
ctx.status(err.statusCode || 500);
|
|
18
|
+
return ctx.json({
|
|
19
|
+
error_code: err.name,
|
|
20
|
+
error: err.message,
|
|
21
|
+
data: err.data
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
console.error(err);
|
|
25
|
+
ctx.status(500);
|
|
26
|
+
return ctx.json({ error: err.message });
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unconventional",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A complete server built on Cloudflare Pages Functions",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/cloudflare-extension/unconventional.git"
|
|
12
|
-
},
|
|
13
5
|
"keywords": [
|
|
14
6
|
"cloudflare",
|
|
15
7
|
"pages",
|
|
@@ -18,12 +10,46 @@
|
|
|
18
10
|
"edge",
|
|
19
11
|
"server",
|
|
20
12
|
"express",
|
|
21
|
-
"crud"
|
|
13
|
+
"crud",
|
|
14
|
+
"rest",
|
|
15
|
+
"api"
|
|
22
16
|
],
|
|
23
17
|
"author": "Dean Mauro",
|
|
24
18
|
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/cloudflare-extension/unconventional.git"
|
|
22
|
+
},
|
|
25
23
|
"bugs": {
|
|
26
24
|
"url": "https://github.com/cloudflare-extension/unconventional/issues"
|
|
27
25
|
},
|
|
28
|
-
"homepage": "https://github.com/cloudflare-extension/unconventional#readme"
|
|
26
|
+
"homepage": "https://github.com/cloudflare-extension/unconventional#readme",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"main": "dist/index.js",
|
|
31
|
+
"types": "dist/index.d.ts",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "tsc --build --clean",
|
|
34
|
+
"build": "rm -rf dist && tsc --build",
|
|
35
|
+
"watch": "tsc --watch",
|
|
36
|
+
"lint": "eslint . --ext js,jsx,ts,tsx",
|
|
37
|
+
"format": "eslint . --ext js,jsx,ts,tsx --fix",
|
|
38
|
+
"docs:dev": "vitepress dev docs",
|
|
39
|
+
"docs:build": "vitepress build docs",
|
|
40
|
+
"docs:preview": "vitepress preview docs",
|
|
41
|
+
"docs:serve": "vitepress serve docs"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"hono": "^3.11.4",
|
|
45
|
+
"unconventional-pg-queries": "^0.0.5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@cloudflare/workers-types": "^4.20231121.0",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
|
50
|
+
"@typescript-eslint/parser": "^5.62.0",
|
|
51
|
+
"hono": "^3.11.4",
|
|
52
|
+
"typescript": "^5.3.3",
|
|
53
|
+
"unconventional-pg-queries": "^0.0.5"
|
|
54
|
+
}
|
|
29
55
|
}
|