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.
@@ -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;
@@ -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 (const item of dataArray) {
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 && key in item) {
217
- item[key] = undefined;
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]) {
@@ -1,4 +1,5 @@
1
1
  export * from './abstractbase.controller';
2
+ export * from './abstractbase.middleware';
2
3
  export * from './api-error';
3
4
  export * from './base.cache';
4
5
  export * from './base.model';
@@ -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: any, ctx: any) => any;
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.11.0",
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 && git push origin && git push origin --tags",
43
- "release:minor": "changelog -m && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version minor && git push origin && git push origin --tags",
44
- "release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin && git push origin --tags"
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",