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,56 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { ConflictResolution } from "unconventional-pg-queries";
|
|
3
|
+
import { Env } from "../types/api.types";
|
|
4
|
+
import BaseModel from "./base.model";
|
|
5
|
+
export interface PageConfig {
|
|
6
|
+
limit?: number;
|
|
7
|
+
cursor?: string;
|
|
8
|
+
sort?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FilterConfig {
|
|
11
|
+
filter?: string;
|
|
12
|
+
expand?: string;
|
|
13
|
+
}
|
|
14
|
+
interface DownloadResponse {
|
|
15
|
+
filename: string;
|
|
16
|
+
buffer: ArrayBufferLike;
|
|
17
|
+
}
|
|
18
|
+
export interface UpsertConfig<M extends typeof BaseModel> {
|
|
19
|
+
constraint?: Array<keyof InstanceType<M>>;
|
|
20
|
+
action?: ConflictResolution;
|
|
21
|
+
}
|
|
22
|
+
export interface ServiceConfig<M extends typeof BaseModel> {
|
|
23
|
+
cache?: KVNamespace;
|
|
24
|
+
cacheTTL?: number;
|
|
25
|
+
upsertConfig?: UpsertConfig<M>;
|
|
26
|
+
}
|
|
27
|
+
export interface ControllerOptions {
|
|
28
|
+
cache?: boolean;
|
|
29
|
+
cacheTTL?: number;
|
|
30
|
+
}
|
|
31
|
+
export type PI<T extends abstract new (...args: any[]) => any> = Partial<InstanceType<T>>;
|
|
32
|
+
export type I<T extends abstract new (...args: any[]) => any> = InstanceType<T>;
|
|
33
|
+
export interface ControllerMethodOptions<M extends typeof BaseModel, V extends Env, U extends PI<M> | PI<M>[] = PI<M>, W extends I<M> | I<M>[] = I<M>> {
|
|
34
|
+
before?: BodyProcessor<M, V, U>;
|
|
35
|
+
after?: ResponseProcessor<M, V, U, W>;
|
|
36
|
+
cache?: boolean;
|
|
37
|
+
cacheTTL?: number;
|
|
38
|
+
limitOverride?: boolean;
|
|
39
|
+
upsertContraint?: Array<keyof InstanceType<M>>;
|
|
40
|
+
}
|
|
41
|
+
export interface DownloadMethodOptions<M extends typeof BaseModel, V extends Env, U extends PI<M> | PI<M>[] = any, W extends I<M> | I<M>[] = any> {
|
|
42
|
+
download?: DownloadProcessor<M, V, U, W>;
|
|
43
|
+
}
|
|
44
|
+
export interface RequestContext<M extends typeof BaseModel, V extends Env, U extends PI<M> | PI<M>[] = PI<M>> {
|
|
45
|
+
headers: Headers;
|
|
46
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
47
|
+
body: U;
|
|
48
|
+
params: Record<string, string>;
|
|
49
|
+
queries: Record<string, string | string[]>;
|
|
50
|
+
env: V['Bindings'];
|
|
51
|
+
get: <Key extends keyof V['Variables']>(key: Key) => V['Variables'][Key];
|
|
52
|
+
}
|
|
53
|
+
export type BodyProcessor<M extends typeof BaseModel, V extends Env, U extends PI<M> | PI<M>[] = PI<M>> = (requestContext: RequestContext<M, V, U>) => Promise<void>;
|
|
54
|
+
export type ResponseProcessor<M extends typeof BaseModel, V extends Env = Env, U extends PI<M> | PI<M>[] = PI<M>, W extends I<M> | I<M>[] = I<M>> = (requestContext: RequestContext<M, V, U>, response: W) => Promise<void>;
|
|
55
|
+
export type DownloadProcessor<M extends typeof BaseModel, V extends Env = Env, U extends PI<M> | PI<M>[] = any, W = any> = (requestContext: RequestContext<M, V, U>, response: W) => Promise<DownloadResponse>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import BaseModel from "./base.model";
|
|
2
|
+
import { FilterConfig, PageConfig, ServiceConfig } from "./base.options";
|
|
3
|
+
export default class BaseService<M extends typeof BaseModel> {
|
|
4
|
+
private model;
|
|
5
|
+
constructor(model: M);
|
|
6
|
+
static for<M extends typeof BaseModel>(model: M): BaseService<M>;
|
|
7
|
+
create(data: Partial<InstanceType<M>>, config?: ServiceConfig<M>): Promise<InstanceType<M>>;
|
|
8
|
+
createMany(data: Partial<InstanceType<M>>[], config?: ServiceConfig<M>): Promise<InstanceType<M>[]>;
|
|
9
|
+
get(identifier: string | number, config?: FilterConfig & ServiceConfig<M>): Promise<InstanceType<M>>;
|
|
10
|
+
getAll(config?: PageConfig & FilterConfig & ServiceConfig<M>): Promise<Array<InstanceType<M>>>;
|
|
11
|
+
update(identifier: string, data: Partial<InstanceType<M>>, config?: ServiceConfig<M>): Promise<InstanceType<M>>;
|
|
12
|
+
updateMany(data: Partial<InstanceType<M>>[], config?: ServiceConfig<M>): Promise<InstanceType<M>[]>;
|
|
13
|
+
delete(identifier: string, config?: ServiceConfig<M>): Promise<InstanceType<M>>;
|
|
14
|
+
deleteMany(config?: FilterConfig & ServiceConfig<M>): Promise<InstanceType<M>[]>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { SqlWhereOperator } from "unconventional-pg-queries";
|
|
2
|
+
import { APIError } from "./api-error";
|
|
3
|
+
import BaseCache from "./base.cache";
|
|
4
|
+
import { buildFilter } from "../utils/db.utils";
|
|
5
|
+
export default class BaseService {
|
|
6
|
+
model;
|
|
7
|
+
constructor(model) {
|
|
8
|
+
this.model = model;
|
|
9
|
+
}
|
|
10
|
+
static for(model) {
|
|
11
|
+
return new this(model);
|
|
12
|
+
}
|
|
13
|
+
async create(data, config) {
|
|
14
|
+
const response = await this.model.create(data, config?.upsertConfig);
|
|
15
|
+
if (!response) {
|
|
16
|
+
throw APIError.errResourceCreationFailed(`Failed to create ${this.model.name}`);
|
|
17
|
+
}
|
|
18
|
+
else if (config?.cache) {
|
|
19
|
+
await BaseCache.clearPage(config.cache, this.model);
|
|
20
|
+
await BaseCache.setModel(config.cache, response, undefined, config.cacheTTL);
|
|
21
|
+
}
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
async createMany(data, config) {
|
|
25
|
+
const response = await this.model.createMany(data, config?.upsertConfig);
|
|
26
|
+
if (!response) {
|
|
27
|
+
throw APIError.errResourceCreationFailed(`Failed to create ${this.model.name}s`);
|
|
28
|
+
}
|
|
29
|
+
else if (config?.cache) {
|
|
30
|
+
const cache = config.cache;
|
|
31
|
+
await BaseCache.clearPage(cache, this.model);
|
|
32
|
+
await Promise.all(response.map(item => BaseCache.setModel(cache, item, undefined, config.cacheTTL)));
|
|
33
|
+
}
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
async get(identifier, config) {
|
|
37
|
+
const { cache, cacheTTL, ...modifiers } = config || {};
|
|
38
|
+
let entity = null;
|
|
39
|
+
if (cache) {
|
|
40
|
+
entity = await BaseCache.getModel(cache, this.model, identifier, modifiers);
|
|
41
|
+
}
|
|
42
|
+
if (!entity) {
|
|
43
|
+
if (!isNaN(+identifier)) {
|
|
44
|
+
entity = await this.model.findById(identifier, modifiers);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const keyFilter = buildFilter(this.model.keyField, SqlWhereOperator.Eq, identifier);
|
|
48
|
+
const filter = modifiers.filter ? `${modifiers.filter} AND ${keyFilter}` : keyFilter;
|
|
49
|
+
entity = await this.model.findOne({ ...modifiers, filter });
|
|
50
|
+
}
|
|
51
|
+
if (!entity)
|
|
52
|
+
throw APIError.errNotFound(`No ${this.model.name} found with id ${identifier}`);
|
|
53
|
+
if (cache) {
|
|
54
|
+
await BaseCache.setModel(cache, entity, modifiers, cacheTTL);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return entity;
|
|
58
|
+
}
|
|
59
|
+
async getAll(config) {
|
|
60
|
+
const { cache, cacheTTL, ...modifiers } = config || {};
|
|
61
|
+
let page = null;
|
|
62
|
+
if (cache) {
|
|
63
|
+
page = await BaseCache.getPage(cache, this.model, modifiers);
|
|
64
|
+
}
|
|
65
|
+
if (!page) {
|
|
66
|
+
const limit = modifiers.limit || 12;
|
|
67
|
+
page = await this.model.findMany({ limit, ...modifiers });
|
|
68
|
+
if (cache) {
|
|
69
|
+
await BaseCache.setPage(cache, page, modifiers, cacheTTL);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return page;
|
|
73
|
+
}
|
|
74
|
+
async update(identifier, data, config) {
|
|
75
|
+
const response = await this.model.update(identifier, data);
|
|
76
|
+
if (!response) {
|
|
77
|
+
throw APIError.errNotFound(`No ${this.model.name} found with id ${identifier}`);
|
|
78
|
+
}
|
|
79
|
+
else if (config?.cache) {
|
|
80
|
+
await BaseCache.clearModel(config.cache, response);
|
|
81
|
+
await BaseCache.setModel(config.cache, response, undefined, config.cacheTTL);
|
|
82
|
+
}
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
async updateMany(data, config) {
|
|
86
|
+
const response = await this.model.updateMany(data);
|
|
87
|
+
if (!response) {
|
|
88
|
+
throw APIError.errResourceUpdateFailed(`Failed to update ${this.model.name}s`);
|
|
89
|
+
}
|
|
90
|
+
else if (config?.cache) {
|
|
91
|
+
const cache = config.cache;
|
|
92
|
+
await Promise.all(response.map(item => BaseCache.clearModel(cache, item)));
|
|
93
|
+
await Promise.all(response.map(item => BaseCache.setModel(cache, item, undefined, config.cacheTTL)));
|
|
94
|
+
}
|
|
95
|
+
return response;
|
|
96
|
+
}
|
|
97
|
+
async delete(identifier, config) {
|
|
98
|
+
const response = await this.model.delete(identifier);
|
|
99
|
+
if (!response) {
|
|
100
|
+
throw APIError.errNotFound(`No ${this.model.name} found with id ${identifier}`);
|
|
101
|
+
}
|
|
102
|
+
else if (config?.cache) {
|
|
103
|
+
await BaseCache.clearModel(config.cache, response);
|
|
104
|
+
await BaseCache.clearPage(config.cache, this.model);
|
|
105
|
+
}
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
async deleteMany(config) {
|
|
109
|
+
if (!config?.filter)
|
|
110
|
+
throw APIError.errBadRequest("No filter provided");
|
|
111
|
+
const response = await this.model.deleteMany(config);
|
|
112
|
+
if (!response) {
|
|
113
|
+
throw APIError.errResourceDeletionFailed(`Failed to delete ${this.model.name}s`);
|
|
114
|
+
}
|
|
115
|
+
else if (config?.cache) {
|
|
116
|
+
const cache = config.cache;
|
|
117
|
+
await BaseCache.clearPage(config.cache, this.model);
|
|
118
|
+
await Promise.all(response.map(item => BaseCache.clearModel(cache, item)));
|
|
119
|
+
}
|
|
120
|
+
return response;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
async function PGFn(binding, endpoint, query) {
|
|
2
|
+
const response = await binding.fetch(new Request(`http://worker/${endpoint}`, {
|
|
3
|
+
method: 'POST',
|
|
4
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5
|
+
body: JSON.stringify(query)
|
|
6
|
+
}));
|
|
7
|
+
if (!response.ok)
|
|
8
|
+
return null;
|
|
9
|
+
const body = await response.json();
|
|
10
|
+
return body;
|
|
11
|
+
}
|
|
12
|
+
export const PGFactory = (ctx) => {
|
|
13
|
+
return {
|
|
14
|
+
fetch: async (query) => {
|
|
15
|
+
return await PGFn(ctx.env.DB_PROXY, 'query', query);
|
|
16
|
+
},
|
|
17
|
+
raw: async (query) => {
|
|
18
|
+
return await PGFn(ctx.env.DB_PROXY, 'text', { text: query });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import BaseModel from '../core/base.model';
|
|
2
|
+
import { IndexDefinition, IndexOptions, TypedClassDecorator } from '../types/decorator.types';
|
|
3
|
+
export declare function index<T extends typeof BaseModel>(fields: IndexDefinition<InstanceType<T>>, options?: IndexOptions): TypedClassDecorator<T>;
|
|
4
|
+
export { index as Index };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import BaseModel from '../core/base.model';
|
|
2
|
+
import type { PropOptions } from '../types/decorator.types';
|
|
3
|
+
declare function prop<Target = any, Source extends typeof BaseModel = any, Pivot extends typeof BaseModel = any>(options?: PropOptions<Target, Source, Pivot>): PropertyDecorator;
|
|
4
|
+
export { prop as Prop, prop };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function prop(options) {
|
|
2
|
+
return (target, key) => {
|
|
3
|
+
const parent = Object.getPrototypeOf(target.constructor);
|
|
4
|
+
const child = target.constructor;
|
|
5
|
+
if (child.schema === parent.schema) {
|
|
6
|
+
child.schema = parent.schema
|
|
7
|
+
? { indexes: [...parent.schema.indexes], props: { ...parent.schema.props }, timestamped: parent.schema.timestamped }
|
|
8
|
+
: { indexes: [], props: {} };
|
|
9
|
+
}
|
|
10
|
+
child.schema.props[key] = options || {};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export { prop as Prop, prop };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getName(target: any): any;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import type { Env } from "./types/api.types";
|
|
4
|
+
import type { ServerConfig } from "./types/server.types";
|
|
5
|
+
export declare class BackendServer {
|
|
6
|
+
private _app;
|
|
7
|
+
configured: boolean;
|
|
8
|
+
constructor(config?: ServerConfig);
|
|
9
|
+
get app(): Hono<Env, any, any>;
|
|
10
|
+
configure(config: ServerConfig): this;
|
|
11
|
+
start(): (eventContext: import("hono/cloudflare-pages").EventContext<{}, any, {}>) => Response | Promise<Response>;
|
|
12
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { handle } from "hono/cloudflare-pages";
|
|
3
|
+
import { bindDatabase, defaultCors, defaultErrorHandler } from "./utils";
|
|
4
|
+
import { cors } from "hono/cors";
|
|
5
|
+
export class BackendServer {
|
|
6
|
+
_app;
|
|
7
|
+
configured = false;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this._app = new Hono();
|
|
10
|
+
if (config)
|
|
11
|
+
this.configure(config);
|
|
12
|
+
}
|
|
13
|
+
get app() {
|
|
14
|
+
return this._app;
|
|
15
|
+
}
|
|
16
|
+
configure(config) {
|
|
17
|
+
if (this.configured)
|
|
18
|
+
throw new Error("Cannot reconfigure server");
|
|
19
|
+
if (config.basePath)
|
|
20
|
+
this._app = this._app.basePath(config.basePath);
|
|
21
|
+
this._app.use(bindDatabase(config.getDB));
|
|
22
|
+
this._app.use(config.cors ? cors(config.cors) : defaultCors);
|
|
23
|
+
this._app.onError(defaultErrorHandler);
|
|
24
|
+
if (config.middleware)
|
|
25
|
+
this._app.use(...config.middleware);
|
|
26
|
+
this.configured = true;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
start() {
|
|
30
|
+
return handle(this._app);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { Handler, Input } from "hono";
|
|
3
|
+
export declare enum Query {
|
|
4
|
+
Expand = "expand"
|
|
5
|
+
}
|
|
6
|
+
export declare enum CachePrefix {
|
|
7
|
+
Record = "rec_",
|
|
8
|
+
Page = "page_"
|
|
9
|
+
}
|
|
10
|
+
export declare const DefaultCacheTTL = 900;
|
|
11
|
+
export declare const MaxCacheTTL = 2147483647;
|
|
12
|
+
export type Bindings = {
|
|
13
|
+
CACHE: KVNamespace;
|
|
14
|
+
STORAGE: KVNamespace;
|
|
15
|
+
DB_PROXY: Fetcher;
|
|
16
|
+
};
|
|
17
|
+
export declare enum BaseEnvKey {
|
|
18
|
+
ownerId = "ownerId",
|
|
19
|
+
isPrivileged = "isPrivileged"
|
|
20
|
+
}
|
|
21
|
+
export type Variables = {
|
|
22
|
+
[BaseEnvKey.ownerId]?: number;
|
|
23
|
+
[BaseEnvKey.isPrivileged]?: boolean;
|
|
24
|
+
};
|
|
25
|
+
export type Env<V extends object = {}, B extends object = {}> = {
|
|
26
|
+
Bindings: Bindings & B;
|
|
27
|
+
Variables: Variables & V;
|
|
28
|
+
};
|
|
29
|
+
export interface BaseHandler<E extends Env = Env, P extends string = any, I extends Input = Input> extends Handler<E, P, I> {
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export var Query;
|
|
2
|
+
(function (Query) {
|
|
3
|
+
Query["Expand"] = "expand";
|
|
4
|
+
})(Query || (Query = {}));
|
|
5
|
+
export var CachePrefix;
|
|
6
|
+
(function (CachePrefix) {
|
|
7
|
+
CachePrefix["Record"] = "rec_";
|
|
8
|
+
CachePrefix["Page"] = "page_";
|
|
9
|
+
})(CachePrefix || (CachePrefix = {}));
|
|
10
|
+
export const DefaultCacheTTL = 900;
|
|
11
|
+
export const MaxCacheTTL = 2147483647;
|
|
12
|
+
export var BaseEnvKey;
|
|
13
|
+
(function (BaseEnvKey) {
|
|
14
|
+
BaseEnvKey["ownerId"] = "ownerId";
|
|
15
|
+
BaseEnvKey["isPrivileged"] = "isPrivileged";
|
|
16
|
+
})(BaseEnvKey || (BaseEnvKey = {}));
|
|
17
|
+
;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { Context } from "hono";
|
|
3
|
+
import { OneOrMany, QueryDefinition } from "unconventional-pg-queries";
|
|
4
|
+
export declare const ValidSqlOperators: string[];
|
|
5
|
+
export declare const NullSqlOperators: string[];
|
|
6
|
+
export declare const ValidSortDirections: string[];
|
|
7
|
+
export declare const andOrPattern: RegExp;
|
|
8
|
+
export declare enum SqlAction {
|
|
9
|
+
Select = "SELECT",
|
|
10
|
+
Insert = "INSERT",
|
|
11
|
+
Update = "UPDATE",
|
|
12
|
+
Delete = "DELETE"
|
|
13
|
+
}
|
|
14
|
+
export interface DBCall extends QueryDefinition {
|
|
15
|
+
action: SqlAction;
|
|
16
|
+
type: OneOrMany;
|
|
17
|
+
}
|
|
18
|
+
export type DBProxy<I extends string | DBCall> = <R extends object>(query: I) => Promise<R | null>;
|
|
19
|
+
export interface DB {
|
|
20
|
+
fetch: DBProxy<DBCall>;
|
|
21
|
+
raw: DBProxy<string>;
|
|
22
|
+
}
|
|
23
|
+
export type DBBinding = Fetcher | D1Database | KVNamespace;
|
|
24
|
+
export type DBFactory = (ctx: Context) => DB;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SqlDirection, SqlWhereOperator } from "unconventional-pg-queries";
|
|
2
|
+
export const ValidSqlOperators = Object.values(SqlWhereOperator);
|
|
3
|
+
export const NullSqlOperators = [SqlWhereOperator.IsNull, SqlWhereOperator.IsNotNull];
|
|
4
|
+
export const ValidSortDirections = Object.values(SqlDirection);
|
|
5
|
+
export const andOrPattern = /\s(?=AND|OR)/;
|
|
6
|
+
export var SqlAction;
|
|
7
|
+
(function (SqlAction) {
|
|
8
|
+
SqlAction["Select"] = "SELECT";
|
|
9
|
+
SqlAction["Insert"] = "INSERT";
|
|
10
|
+
SqlAction["Update"] = "UPDATE";
|
|
11
|
+
SqlAction["Delete"] = "DELETE";
|
|
12
|
+
})(SqlAction || (SqlAction = {}));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import BaseModel from "../core/base.model";
|
|
2
|
+
export declare type TypedClassDecorator<T> = (target: T) => T | void;
|
|
3
|
+
export type IndexDefinition<T> = {
|
|
4
|
+
[P in keyof T]?: 1 | -1;
|
|
5
|
+
};
|
|
6
|
+
export interface IndexOptions {
|
|
7
|
+
unique?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface IndexSummary<T = any> {
|
|
10
|
+
definition: IndexDefinition<T>;
|
|
11
|
+
options?: IndexOptions;
|
|
12
|
+
}
|
|
13
|
+
export declare enum RelationType {
|
|
14
|
+
HasOne = 0,
|
|
15
|
+
HasMany = 1,
|
|
16
|
+
ManyToMany = 2,
|
|
17
|
+
BelongsTo = 3
|
|
18
|
+
}
|
|
19
|
+
export interface RelationPivot<T extends typeof BaseModel = any> {
|
|
20
|
+
model: T;
|
|
21
|
+
from: keyof InstanceType<T>;
|
|
22
|
+
to: keyof InstanceType<T>;
|
|
23
|
+
}
|
|
24
|
+
export interface Relation<Target extends typeof BaseModel = any, Source extends typeof BaseModel = any, Pivot extends typeof BaseModel = any> {
|
|
25
|
+
type: RelationType;
|
|
26
|
+
model: Target;
|
|
27
|
+
from: keyof InstanceType<Source>;
|
|
28
|
+
to: keyof InstanceType<Target>;
|
|
29
|
+
through?: RelationPivot<Pivot>;
|
|
30
|
+
}
|
|
31
|
+
export interface PropOptions<T = any, S extends typeof BaseModel = any, P extends typeof BaseModel = any> {
|
|
32
|
+
required?: boolean;
|
|
33
|
+
unique?: boolean;
|
|
34
|
+
enum?: any;
|
|
35
|
+
default?: T | (() => T);
|
|
36
|
+
relation?: T extends typeof BaseModel ? Relation<T, S, P> : never;
|
|
37
|
+
}
|
|
38
|
+
export type PropSummary<Target = any, Source extends typeof BaseModel = any> = Record<keyof Source, PropOptions<Target, Source>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var RelationType;
|
|
2
|
+
(function (RelationType) {
|
|
3
|
+
RelationType[RelationType["HasOne"] = 0] = "HasOne";
|
|
4
|
+
RelationType[RelationType["HasMany"] = 1] = "HasMany";
|
|
5
|
+
RelationType[RelationType["ManyToMany"] = 2] = "ManyToMany";
|
|
6
|
+
RelationType[RelationType["BelongsTo"] = 3] = "BelongsTo";
|
|
7
|
+
})(RelationType || (RelationType = {}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MiddlewareHandler } from "hono";
|
|
2
|
+
import { DBFactory } from "./db.types";
|
|
3
|
+
import { cors } from "hono/cors";
|
|
4
|
+
export interface ServerConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
basePath?: string;
|
|
7
|
+
middleware?: MiddlewareHandler[];
|
|
8
|
+
cors?: Parameters<typeof cors>[0];
|
|
9
|
+
getDB: DBFactory;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AndOr } from "unconventional-pg-queries";
|
|
2
|
+
export declare function parseExpandString(expand: string): string[];
|
|
3
|
+
export declare function splitExpandUnit(input: string): {
|
|
4
|
+
parent: string;
|
|
5
|
+
children: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function replaceFilter(filter: string | string[] | undefined, target: string, replacement: string, andOr?: AndOr): string;
|
|
8
|
+
export declare function hasFilter(filter: string | string[] | undefined, target: string): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AndOr } from "unconventional-pg-queries";
|
|
2
|
+
import { andOrPattern } from "../types/db.types";
|
|
3
|
+
const defaultTarget = ',';
|
|
4
|
+
const openBracket = '[';
|
|
5
|
+
const closeBracket = ']';
|
|
6
|
+
export function parseExpandString(expand) {
|
|
7
|
+
const text = expand.trim();
|
|
8
|
+
if (!text)
|
|
9
|
+
return [];
|
|
10
|
+
const result = [];
|
|
11
|
+
let word = '';
|
|
12
|
+
let bracketCount = 0;
|
|
13
|
+
for (const char of text) {
|
|
14
|
+
if (bracketCount === 0 && char === defaultTarget) {
|
|
15
|
+
result.push(`${word}`);
|
|
16
|
+
word = '';
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
word += char;
|
|
20
|
+
if (char === openBracket)
|
|
21
|
+
bracketCount++;
|
|
22
|
+
else if (char === closeBracket)
|
|
23
|
+
bracketCount--;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
result.push(`${word}`);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
export function splitExpandUnit(input) {
|
|
30
|
+
const index = input.indexOf(openBracket);
|
|
31
|
+
if (index === -1)
|
|
32
|
+
return { parent: input, children: '' };
|
|
33
|
+
return { parent: input.slice(0, index), children: input.slice(index + 1, -1) };
|
|
34
|
+
}
|
|
35
|
+
export function replaceFilter(filter, target, replacement, andOr = AndOr.And) {
|
|
36
|
+
if (!filter)
|
|
37
|
+
return replacement;
|
|
38
|
+
const otherFilters = filter.split(andOrPattern).flatMap(x => x.includes(target) ? [] : x);
|
|
39
|
+
otherFilters.push(`${otherFilters.length ? `${andOr} ` : ''}${replacement}`);
|
|
40
|
+
return otherFilters.join(' ');
|
|
41
|
+
}
|
|
42
|
+
export function hasFilter(filter, target) {
|
|
43
|
+
if (!filter)
|
|
44
|
+
return false;
|
|
45
|
+
return filter.split(andOrPattern).some(x => x.startsWith(target));
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const equalArrays = (a, b) => {
|
|
2
|
+
if (a.length !== b.length)
|
|
3
|
+
return false;
|
|
4
|
+
const clone = [...b];
|
|
5
|
+
for (const key of a) {
|
|
6
|
+
const idx = b.indexOf(key);
|
|
7
|
+
if (idx > -1)
|
|
8
|
+
clone.splice(idx, 1);
|
|
9
|
+
else {
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return clone.length === 0;
|
|
14
|
+
};
|
|
15
|
+
export function isEmpty(obj) {
|
|
16
|
+
for (const key in obj)
|
|
17
|
+
if (obj[key] !== undefined)
|
|
18
|
+
return false;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DefaultCacheTTL } from "../types/api.types";
|
|
2
|
+
export async function getCachedOrFetch(cache, key, fetchFn, ttl = DefaultCacheTTL) {
|
|
3
|
+
if (cache) {
|
|
4
|
+
const cachedResponse = await cache.get(key);
|
|
5
|
+
if (cachedResponse) {
|
|
6
|
+
return JSON.parse(cachedResponse);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const response = await fetchFn();
|
|
10
|
+
if (response && cache) {
|
|
11
|
+
await cache.put(key, JSON.stringify(response), { expirationTtl: ttl });
|
|
12
|
+
}
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sha256(value: string): Promise<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function sha256(value) {
|
|
2
|
+
var buffer = new TextEncoder().encode(value);
|
|
3
|
+
return crypto.subtle.digest("SHA-256", buffer).then(hex);
|
|
4
|
+
}
|
|
5
|
+
function hex(buffer) {
|
|
6
|
+
return [...new Uint8Array(buffer)]
|
|
7
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|