unconventional 1.11.0 → 1.13.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/dist/core/abstractbase.middleware.d.ts +21 -0
- package/dist/core/abstractbase.middleware.js +58 -0
- package/dist/core/base.model.js +11 -3
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/types/decorator.types.d.ts +2 -2
- package/dist/utils/array.utils.d.ts +2 -0
- package/dist/utils/array.utils.js +6 -1
- package/dist/utils/middleware.utils.d.ts +5 -0
- package/dist/utils/middleware.utils.js +9 -1
- package/dist/utils/server.utils.d.ts +6 -1
- package/package.json +4 -4
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseModel } from "./base.model";
|
|
2
|
+
/** A predicate that matches a model to a given condition */
|
|
3
|
+
export type ModelPredicate = (model: typeof BaseModel) => boolean;
|
|
4
|
+
/** Middleware base class with methods that can find the ancestors of a model */
|
|
5
|
+
export declare class AbstractBaseMiddleware<M extends typeof BaseModel> {
|
|
6
|
+
protected model: M;
|
|
7
|
+
constructor(model: M);
|
|
8
|
+
/**
|
|
9
|
+
* Finds an ancestor that matches the predicate
|
|
10
|
+
* @param id - The ID of the model to find the ancestor for
|
|
11
|
+
* @param predicate - The predicate to match the ancestor
|
|
12
|
+
* @returns The ancestor that matches the predicate or null if no ancestor is found
|
|
13
|
+
*/
|
|
14
|
+
findAncestor(id: string | number, predicate: ModelPredicate): Promise<any | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Builds the relation path to reach an ancestor that matches the predicate
|
|
17
|
+
* @param predicate - The predicate to match the ancestor
|
|
18
|
+
* @returns The relation path (e.g. ['comments', 'post', 'user']) to the ancestor or null if no ancestor is found
|
|
19
|
+
*/
|
|
20
|
+
protected buildAncestralPath(predicate: ModelPredicate): string[] | null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AbstractBaseMiddleware = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
/** Middleware base class with methods that can find the ancestors of a model */
|
|
7
|
+
class AbstractBaseMiddleware {
|
|
8
|
+
model;
|
|
9
|
+
constructor(model) {
|
|
10
|
+
this.model = model;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Finds an ancestor that matches the predicate
|
|
14
|
+
* @param id - The ID of the model to find the ancestor for
|
|
15
|
+
* @param predicate - The predicate to match the ancestor
|
|
16
|
+
* @returns The ancestor that matches the predicate or null if no ancestor is found
|
|
17
|
+
*/
|
|
18
|
+
async findAncestor(id, predicate) {
|
|
19
|
+
const expansion = this.buildAncestralPath(predicate);
|
|
20
|
+
if (!expansion)
|
|
21
|
+
return null;
|
|
22
|
+
const result = await this.model.findById(id, {
|
|
23
|
+
expand: (0, utils_1.buildExpansionString)(expansion)
|
|
24
|
+
});
|
|
25
|
+
if (!result)
|
|
26
|
+
return null;
|
|
27
|
+
// Navigate through the expanded chain to get the owner
|
|
28
|
+
return expansion.reduce((obj, key) => obj?.[key], result);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the relation path to reach an ancestor that matches the predicate
|
|
32
|
+
* @param predicate - The predicate to match the ancestor
|
|
33
|
+
* @returns The relation path (e.g. ['comments', 'post', 'user']) to the ancestor or null if no ancestor is found
|
|
34
|
+
*/
|
|
35
|
+
buildAncestralPath(predicate) {
|
|
36
|
+
let currentModel = this.model;
|
|
37
|
+
const path = [];
|
|
38
|
+
const visited = new Set();
|
|
39
|
+
while (!visited.has(currentModel)) {
|
|
40
|
+
// Found model that matches predicate
|
|
41
|
+
if (predicate(currentModel)) {
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
visited.add(currentModel);
|
|
45
|
+
// Find first BelongsTo relation
|
|
46
|
+
const belongsToRelation = Object.entries(currentModel.schema.props)
|
|
47
|
+
.find(([_, prop]) => prop.relation?.type === types_1.RelationType.BelongsTo);
|
|
48
|
+
if (!belongsToRelation)
|
|
49
|
+
return null;
|
|
50
|
+
const [relationKey, prop] = belongsToRelation;
|
|
51
|
+
const parentModel = (0, utils_1.getRelationModel)(prop.relation.model);
|
|
52
|
+
path.push(relationKey);
|
|
53
|
+
currentModel = parentModel;
|
|
54
|
+
}
|
|
55
|
+
return null; // Circular reference found or no matching owner
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.AbstractBaseMiddleware = AbstractBaseMiddleware;
|
package/dist/core/base.model.js
CHANGED
|
@@ -210,11 +210,19 @@ class BaseModel extends Document {
|
|
|
210
210
|
return;
|
|
211
211
|
const dataArray = Array.isArray(data) ? data : [data];
|
|
212
212
|
const propSummary = Object.entries(this.schema.props);
|
|
213
|
-
for (
|
|
213
|
+
for (let item of dataArray) {
|
|
214
214
|
for (const [key, options] of propSummary) {
|
|
215
215
|
// Mask private fields. Skip 1st level if relationsOnly.
|
|
216
|
-
if (!relationsOnly && options.private
|
|
217
|
-
item[key]
|
|
216
|
+
if (!relationsOnly && key in item && options.private != null) {
|
|
217
|
+
if (Array.isArray(item[key])) {
|
|
218
|
+
item[key] = item[key].filter(rel => !(0, utils_1.evaluate)(options.private, item, rel));
|
|
219
|
+
if (item[key].length === 0)
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
else if ((0, utils_1.evaluate)(options.private, item, item[key])) {
|
|
223
|
+
item[key] = undefined;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
218
226
|
}
|
|
219
227
|
// Mask private fields in relations
|
|
220
228
|
if (options.relation && item[key]) {
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./abstractbase.controller"), exports);
|
|
18
|
+
__exportStar(require("./abstractbase.middleware"), exports);
|
|
18
19
|
__exportStar(require("./api-error"), exports);
|
|
19
20
|
__exportStar(require("./base.cache"), exports);
|
|
20
21
|
__exportStar(require("./base.model"), exports);
|
|
@@ -63,9 +63,9 @@ export interface PropOptions<T = any, S extends typeof BaseModel = any, P extend
|
|
|
63
63
|
/** Give the Property a default Value */
|
|
64
64
|
default?: T | (() => T);
|
|
65
65
|
/** Protect field from edits by non-privileged users */
|
|
66
|
-
system?: boolean;
|
|
66
|
+
system?: boolean | ((model: InstanceType<S>, field: T extends typeof BaseModel ? InstanceType<T> : T) => boolean);
|
|
67
67
|
/** Hide field in responses to non-privileged users */
|
|
68
|
-
private?: boolean;
|
|
68
|
+
private?: boolean | ((model: InstanceType<S>, field: T extends typeof BaseModel ? InstanceType<T> : T) => boolean);
|
|
69
69
|
/** Relational field data */
|
|
70
70
|
relation?: T extends typeof BaseModel ? Relation<T, S, P> : never;
|
|
71
71
|
}
|
|
@@ -2,3 +2,5 @@
|
|
|
2
2
|
export declare const equalArrays: <T>(a: T[], b: T[]) => boolean;
|
|
3
3
|
/** Checks whether an object has no keys and/or the value for every key is undefined */
|
|
4
4
|
export declare function isEmpty(obj: any): boolean;
|
|
5
|
+
/** Evaluate a direct or functional value */
|
|
6
|
+
export declare function evaluate<T, F extends (...args: any[]) => T>(value: T | F | undefined, ...args: Parameters<F>): T | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isEmpty = exports.equalArrays = void 0;
|
|
3
|
+
exports.evaluate = exports.isEmpty = exports.equalArrays = void 0;
|
|
4
4
|
/** Compares two arrays and returns true if they contain the same elements, regardless of order */
|
|
5
5
|
const equalArrays = (a, b) => {
|
|
6
6
|
if (a.length !== b.length)
|
|
@@ -26,3 +26,8 @@ function isEmpty(obj) {
|
|
|
26
26
|
return true;
|
|
27
27
|
}
|
|
28
28
|
exports.isEmpty = isEmpty;
|
|
29
|
+
/** Evaluate a direct or functional value */
|
|
30
|
+
function evaluate(value, ...args) {
|
|
31
|
+
return value instanceof Function ? value(...args) : value;
|
|
32
|
+
}
|
|
33
|
+
exports.evaluate = evaluate;
|
|
@@ -5,3 +5,8 @@ import { BaseHandler, Env } from "../types/api.types";
|
|
|
5
5
|
* the logical OR of their results.
|
|
6
6
|
*/
|
|
7
7
|
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>;
|
|
8
|
+
/**
|
|
9
|
+
* Converts an array of relation paths into a nested expansion string
|
|
10
|
+
* @example buildExpansionString(['comments', 'post', 'user']) => 'comments[post[user]]'
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildExpansionString(path: string[]): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Or = void 0;
|
|
3
|
+
exports.buildExpansionString = exports.Or = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Combines any number of RequestHandlers into one function that performs
|
|
6
6
|
* the logical OR of their results.
|
|
@@ -16,3 +16,11 @@ function Or(...methods) {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
exports.Or = Or;
|
|
19
|
+
/**
|
|
20
|
+
* Converts an array of relation paths into a nested expansion string
|
|
21
|
+
* @example buildExpansionString(['comments', 'post', 'user']) => 'comments[post[user]]'
|
|
22
|
+
*/
|
|
23
|
+
function buildExpansionString(path) {
|
|
24
|
+
return path.reduceRight((acc, curr) => acc ? `${curr}[${acc}]` : curr, '');
|
|
25
|
+
}
|
|
26
|
+
exports.buildExpansionString = buildExpansionString;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
1
2
|
import { DBFactory } from "../types";
|
|
3
|
+
import { Context } from "hono";
|
|
4
|
+
import { HTTPResponseError } from "hono/types";
|
|
2
5
|
/** Default CORS middleware permitting all origins and methods */
|
|
3
6
|
export declare const defaultCors: import("hono").MiddlewareHandler;
|
|
4
7
|
/** Binds the database proxy to the BaseModel */
|
|
5
8
|
export declare const bindDatabase: (getDB: DBFactory) => (ctx: any, next: any) => Promise<void>;
|
|
6
9
|
/** Catches and formats errors in a standard way */
|
|
7
|
-
export declare const defaultErrorHandler: (err:
|
|
10
|
+
export declare const defaultErrorHandler: (err: Error | HTTPResponseError, ctx: Context) => Response & import("hono").TypedResponse<{
|
|
11
|
+
error: string;
|
|
12
|
+
}, import("hono/utils/http-status").StatusCode, "json">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unconventional",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A complete server built on Cloudflare Workers/Pages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"docs:build": "vitepress build docs",
|
|
40
40
|
"docs:preview": "vitepress preview docs",
|
|
41
41
|
"docs:serve": "vitepress serve docs",
|
|
42
|
-
"release:major": "changelog -M && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version major
|
|
43
|
-
"release:minor": "changelog -m && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version minor
|
|
44
|
-
"release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch
|
|
42
|
+
"release:major": "changelog -M && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version major",
|
|
43
|
+
"release:minor": "changelog -m && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version minor",
|
|
44
|
+
"release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"hono": "^4",
|