@tstdl/base 0.92.168 → 0.93.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,15 @@
1
+ import { EntityWithoutMetadata, Json, Timestamp, Uuid } from '../orm/index.js';
2
+ import { ActorType, AuditOutcome, AuditSeverity } from './types.js';
3
+ export declare class AuditEvent extends EntityWithoutMetadata {
4
+ timestamp: Timestamp;
5
+ correlationId?: Uuid;
6
+ module: string;
7
+ action: string;
8
+ outcome: AuditOutcome;
9
+ severity: AuditSeverity;
10
+ actorId: string;
11
+ actorType: ActorType;
12
+ targetId: string;
13
+ targetType: string;
14
+ details?: Json<unknown>;
15
+ }
@@ -0,0 +1,73 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { EntityWithoutMetadata, Json, Table, Timestamp, Uuid } from '../orm/index.js';
11
+ import { Enumeration, StringProperty } from '../schema/index.js';
12
+ import { ActorType, AuditOutcome, AuditSeverity } from './types.js';
13
+ let AuditEvent = class AuditEvent extends EntityWithoutMetadata {
14
+ timestamp;
15
+ correlationId;
16
+ module;
17
+ action;
18
+ outcome;
19
+ severity;
20
+ actorId;
21
+ actorType;
22
+ targetId;
23
+ targetType;
24
+ details;
25
+ };
26
+ __decorate([
27
+ Timestamp(),
28
+ __metadata("design:type", Number)
29
+ ], AuditEvent.prototype, "timestamp", void 0);
30
+ __decorate([
31
+ Uuid({ nullable: true }),
32
+ __metadata("design:type", String)
33
+ ], AuditEvent.prototype, "correlationId", void 0);
34
+ __decorate([
35
+ StringProperty(),
36
+ __metadata("design:type", String)
37
+ ], AuditEvent.prototype, "module", void 0);
38
+ __decorate([
39
+ StringProperty(),
40
+ __metadata("design:type", String)
41
+ ], AuditEvent.prototype, "action", void 0);
42
+ __decorate([
43
+ Enumeration(AuditOutcome),
44
+ __metadata("design:type", String)
45
+ ], AuditEvent.prototype, "outcome", void 0);
46
+ __decorate([
47
+ Enumeration(AuditSeverity),
48
+ __metadata("design:type", String)
49
+ ], AuditEvent.prototype, "severity", void 0);
50
+ __decorate([
51
+ StringProperty(),
52
+ __metadata("design:type", String)
53
+ ], AuditEvent.prototype, "actorId", void 0);
54
+ __decorate([
55
+ Enumeration(ActorType),
56
+ __metadata("design:type", String)
57
+ ], AuditEvent.prototype, "actorType", void 0);
58
+ __decorate([
59
+ StringProperty(),
60
+ __metadata("design:type", String)
61
+ ], AuditEvent.prototype, "targetId", void 0);
62
+ __decorate([
63
+ StringProperty(),
64
+ __metadata("design:type", String)
65
+ ], AuditEvent.prototype, "targetType", void 0);
66
+ __decorate([
67
+ Json({ nullable: true }),
68
+ __metadata("design:type", Object)
69
+ ], AuditEvent.prototype, "details", void 0);
70
+ AuditEvent = __decorate([
71
+ Table({ name: 'event' })
72
+ ], AuditEvent);
73
+ export { AuditEvent };
@@ -0,0 +1,42 @@
1
+ import { type Resolvable, type resolveArgumentType } from '../injector/index.js';
2
+ import type { UndefinableJsonObject } from '../types/index.js';
3
+ import type { ActorType } from './types.js';
4
+ import { AuditOutcome, AuditSeverity } from './types.js';
5
+ export type AuditPayload = Partial<{
6
+ correlationId?: string;
7
+ outcome?: AuditOutcome;
8
+ severity?: AuditSeverity;
9
+ actorId?: string;
10
+ actorType?: ActorType;
11
+ targetId?: string;
12
+ targetType?: string;
13
+ details?: UndefinableJsonObject;
14
+ }>;
15
+ export type AuditorArgument = string | string[] | {
16
+ module?: string | string[];
17
+ context?: Partial<AuditPayload>;
18
+ };
19
+ export declare class Auditor implements Resolvable<AuditorArgument> {
20
+ #private;
21
+ readonly module: string[];
22
+ readonly context: Partial<Partial<{
23
+ correlationId?: string;
24
+ outcome?: AuditOutcome;
25
+ severity?: AuditSeverity;
26
+ actorId?: string;
27
+ actorType?: ActorType;
28
+ targetId?: string;
29
+ targetType?: string;
30
+ details?: UndefinableJsonObject;
31
+ }>>;
32
+ get moduleString(): string;
33
+ readonly [resolveArgumentType]: AuditorArgument;
34
+ fork(subModule: string | string[]): Auditor;
35
+ with(context: Partial<AuditPayload>): Auditor;
36
+ withCorrelation(): Auditor;
37
+ log(action: string, data?: AuditPayload): Promise<void>;
38
+ info(action: string, data: AuditPayload): Promise<void>;
39
+ warn(action: string, data: AuditPayload): Promise<void>;
40
+ error(action: string, data: AuditPayload): Promise<void>;
41
+ critical(action: string, data: AuditPayload): Promise<void>;
42
+ }
@@ -0,0 +1,111 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var Auditor_1;
11
+ import { createContextProvider } from '../context/index.js';
12
+ import { Injectable, injectArgument } from '../injector/index.js';
13
+ import { injectRepository } from '../orm/server/index.js';
14
+ import { TRANSACTION_TIMESTAMP } from '../orm/sqls.js';
15
+ import { toArray } from '../utils/array/index.js';
16
+ import { Memoize } from '../utils/function/memoize.js';
17
+ import { filterUndefinedFromRecord, objectKeys } from '../utils/object/object.js';
18
+ import { assertDefinedPass, isArray, isNotArray, isObject, isString } from '../utils/type-guards.js';
19
+ import { AuditEvent } from './audit.model.js';
20
+ import { AuditOutcome, AuditSeverity } from './types.js';
21
+ const { runInAuditorCreationContext, getCurrentAuditorCreationContext, isInAuditorCreationContext } = createContextProvider('AuditorCreation');
22
+ let Auditor = Auditor_1 = class Auditor {
23
+ #repository = injectRepository(AuditEvent);
24
+ #argument = isInAuditorCreationContext() ? injectArgument(this, { optional: true }) : undefined;
25
+ #creationContext = getCurrentAuditorCreationContext();
26
+ module = this.#creationContext?.module ?? moduleFromArgument(this.#argument);
27
+ context = this.#creationContext?.context ?? ((isObject(this.#argument) && isNotArray(this.#argument)) ? (this.#argument.context ?? {}) : {});
28
+ get moduleString() {
29
+ return this.module.join('.');
30
+ }
31
+ fork(subModule) {
32
+ return runInAuditorCreationContext({
33
+ module: [...this.module, ...toArray(subModule)],
34
+ context: this.context,
35
+ }, () => new Auditor_1());
36
+ }
37
+ with(context) {
38
+ return runInAuditorCreationContext({
39
+ module: this.module,
40
+ context: { ...this.context, ...context, details: { ...this.context.details, ...context.details } },
41
+ }, () => new Auditor_1());
42
+ }
43
+ withCorrelation() {
44
+ return this.with({ correlationId: this.context.correlationId ?? crypto.randomUUID() });
45
+ }
46
+ async log(action, data) {
47
+ const mergedData = { ...this.context, ...data, details: filterUndefinedFromRecord({ ...this.context.details, ...data?.details }) };
48
+ await this.#repository.insert({
49
+ timestamp: TRANSACTION_TIMESTAMP,
50
+ correlationId: mergedData.correlationId,
51
+ module: this.moduleString,
52
+ action: action,
53
+ outcome: assertDefinedPass(mergedData.outcome, 'Audit outcome is required'),
54
+ severity: assertDefinedPass(mergedData.severity, 'Audit severity is required'),
55
+ actorId: assertDefinedPass(mergedData.actorId, 'Audit actorId is required'),
56
+ actorType: assertDefinedPass(mergedData.actorType, 'Audit actorType is required'),
57
+ targetId: assertDefinedPass(mergedData.targetId, 'Audit targetId is required'),
58
+ targetType: assertDefinedPass(mergedData.targetType, 'Audit targetType is required'),
59
+ details: (objectKeys(mergedData.details).length > 0) ? mergedData.details : undefined,
60
+ });
61
+ }
62
+ async info(action, data) {
63
+ await this.log(action, {
64
+ ...data,
65
+ severity: AuditSeverity.Info,
66
+ outcome: data.outcome ?? AuditOutcome.Success,
67
+ });
68
+ }
69
+ async warn(action, data) {
70
+ await this.log(action, {
71
+ ...data,
72
+ severity: AuditSeverity.Warn,
73
+ outcome: data.outcome ?? AuditOutcome.Failure,
74
+ });
75
+ }
76
+ async error(action, data) {
77
+ await this.log(action, {
78
+ ...data,
79
+ severity: AuditSeverity.Error,
80
+ outcome: data.outcome ?? AuditOutcome.Failure,
81
+ });
82
+ }
83
+ async critical(action, data) {
84
+ await this.log(action, {
85
+ ...data,
86
+ severity: AuditSeverity.Critical,
87
+ outcome: data.outcome ?? AuditOutcome.Failure,
88
+ });
89
+ }
90
+ };
91
+ __decorate([
92
+ Memoize(),
93
+ __metadata("design:type", String),
94
+ __metadata("design:paramtypes", [])
95
+ ], Auditor.prototype, "moduleString", null);
96
+ Auditor = Auditor_1 = __decorate([
97
+ Injectable()
98
+ ], Auditor);
99
+ export { Auditor };
100
+ function moduleFromArgument(argument) {
101
+ if (isString(argument)) {
102
+ return [argument];
103
+ }
104
+ if (isArray(argument)) {
105
+ return argument;
106
+ }
107
+ if (isObject(argument)) {
108
+ return moduleFromArgument(argument.module);
109
+ }
110
+ return [];
111
+ }
@@ -0,0 +1,3 @@
1
+ export * from './auditor.js';
2
+ export * from './audit.model.js';
3
+ export * from './types.js';
package/audit/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './auditor.js';
2
+ export * from './audit.model.js';
3
+ export * from './types.js';
@@ -0,0 +1,22 @@
1
+ import { type EnumType } from '../enumeration/index.js';
2
+ export declare const AuditSeverity: {
3
+ readonly Info: "info";
4
+ readonly Warn: "warn";
5
+ readonly Error: "error";
6
+ readonly Critical: "critical";
7
+ };
8
+ export type AuditSeverity = EnumType<typeof AuditSeverity>;
9
+ export declare const AuditOutcome: {
10
+ readonly Pending: "pending";
11
+ readonly Success: "success";
12
+ readonly Cancelled: "cancelled";
13
+ readonly Failure: "failure";
14
+ readonly Denied: "denied";
15
+ };
16
+ export type AuditOutcome = EnumType<typeof AuditOutcome>;
17
+ export declare const ActorType: {
18
+ readonly User: "user";
19
+ readonly System: "system";
20
+ readonly ApiKey: "api-key";
21
+ };
22
+ export type ActorType = EnumType<typeof ActorType>;
package/audit/types.js ADDED
@@ -0,0 +1,19 @@
1
+ import { defineEnum } from '../enumeration/index.js';
2
+ export const AuditSeverity = defineEnum('AuditSeverity', {
3
+ Info: 'info',
4
+ Warn: 'warn',
5
+ Error: 'error',
6
+ Critical: 'critical',
7
+ });
8
+ export const AuditOutcome = defineEnum('AuditOutcome', {
9
+ Pending: 'pending',
10
+ Success: 'success',
11
+ Cancelled: 'cancelled',
12
+ Failure: 'failure',
13
+ Denied: 'denied',
14
+ });
15
+ export const ActorType = defineEnum('ActorType', {
16
+ User: 'user',
17
+ System: 'system',
18
+ ApiKey: 'api-key',
19
+ });
package/logger/logger.js CHANGED
@@ -89,7 +89,7 @@ Logger = Logger_1 = __decorate([
89
89
  export { Logger };
90
90
  function moduleFromArgument(argument) {
91
91
  if (isString(argument)) {
92
- return toArray(argument);
92
+ return [argument];
93
93
  }
94
94
  if (isArray(argument)) {
95
95
  return argument;
@@ -13,8 +13,8 @@ export declare class LogManager {
13
13
  /**
14
14
  * Gets the effective log level for a given module path.
15
15
  * It finds the most specific rule matching the module path.
16
- * @param modulePath Array of module names, e.g., ['Api', 'Users']
16
+ * @param module Array of module names, e.g., ['Api', 'Users']
17
17
  * @returns The effective log level
18
18
  */
19
- getModuleLevel(modulePath?: string | string[]): LogLevel;
19
+ getModuleLevel(module?: string | string[]): LogLevel;
20
20
  }
package/logger/manager.js CHANGED
@@ -18,7 +18,16 @@ let LogManager = class LogManager {
18
18
  log(payload) {
19
19
  for (const transport of this.#transports) {
20
20
  if (payload.level <= transport.level) {
21
- transport.log(payload);
21
+ try {
22
+ transport.log(payload);
23
+ }
24
+ catch (err) {
25
+ try {
26
+ const transportName = transport.constructor?.name ?? 'UnknownTransport';
27
+ console.error(`[LogTransport Error] transport=${transportName} error=`, err, 'payload=', payload);
28
+ }
29
+ catch { /* swallow to avoid cascading failures. */ }
30
+ }
22
31
  }
23
32
  }
24
33
  }
@@ -42,14 +51,14 @@ let LogManager = class LogManager {
42
51
  /**
43
52
  * Gets the effective log level for a given module path.
44
53
  * It finds the most specific rule matching the module path.
45
- * @param modulePath Array of module names, e.g., ['Api', 'Users']
54
+ * @param module Array of module names, e.g., ['Api', 'Users']
46
55
  * @returns The effective log level
47
56
  */
48
- getModuleLevel(modulePath) {
49
- if (!isDefined(modulePath) || (modulePath.length == 0)) {
57
+ getModuleLevel(module) {
58
+ if (!isDefined(module) || (module.length == 0)) {
50
59
  return this.#defaultLevel;
51
60
  }
52
- const path = Array.isArray(modulePath) ? modulePath.join('.') : modulePath;
61
+ const path = Array.isArray(module) ? module.join('.') : module;
53
62
  const cachedLevel = this.#moduleLevelCache.get(path);
54
63
  if (isDefined(cachedLevel)) {
55
64
  return cachedLevel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.92.168",
3
+ "version": "0.93.0",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -11,7 +11,7 @@
11
11
  "build:watch": "concurrently --raw --kill-others npm:tsc:watch npm:tsc-alias:watch",
12
12
  "build:production": "rm -rf dist && npm run build && npm run build:production:copy-files",
13
13
  "build:production:copy-files": "cp package.json eslint.config.js tsconfig.server.json dist/ && cp tsconfig.base.json dist/tsconfig.json && npm run copy:orm",
14
- "build:dts": "rm -rf dist && tsc -p tsconfig.dts.json",
14
+ "build:dts": "rm -rf dist && tsc -p tsconfig.dts.json && tsc-alias",
15
15
  "build:docs": "typedoc",
16
16
  "build:docs:watch": "typedoc --watch",
17
17
  "lint": "eslint --cache source/",
@@ -28,6 +28,7 @@
28
28
  "generate:migration:lock": "drizzle-kit generate --config dist/lock/postgres/drizzle.config.js",
29
29
  "generate:readmes": "deno run --allow-run=code2prompt --allow-read --allow-write=source --allow-net=generativelanguage.googleapis.com --allow-env=GEMINI_API_KEY generate-readmes.ts",
30
30
  "generate:readmes:new-only": "npm run generate:readmes -- --skip-existing",
31
+ "generate:llms.md": "npm run build:dts && npm run cleanup:dist && ./scripts/generate-llms-docs.sh",
31
32
  "generate:context7-docs": "npm run build:dts && npm run cleanup:dist && ./scripts/generate-context7-docs.sh",
32
33
  "copy:orm": "npm run copy:orm:document-management && npm run copy:orm:authentication && npm run copy:orm:mail && npm run copy:orm:queue && npm run copy:orm:key-value-store && npm run copy:orm:lock",
33
34
  "copy:orm:document-management": "rm -rf ./dist/document-management/server/drizzle && cp -r ./source/document-management/server/drizzle ./dist/document-management/server/",
@@ -143,7 +144,7 @@
143
144
  },
144
145
  "peerDependencies": {
145
146
  "@google-cloud/storage": "^7.17",
146
- "@google/genai": "^1.17",
147
+ "@google/genai": "^1.19",
147
148
  "@tstdl/angular": "^0.92",
148
149
  "@zxcvbn-ts/core": "^3.0",
149
150
  "@zxcvbn-ts/language-common": "^3.0",
@@ -160,7 +161,7 @@
160
161
  "preact": "^10.27",
161
162
  "preact-render-to-string": "^6.6",
162
163
  "sharp": "^0.34",
163
- "undici": "^7.15",
164
+ "undici": "^7.16",
164
165
  "urlpattern-polyfill": "^10.1"
165
166
  },
166
167
  "peerDependenciesMeta": {
@@ -179,14 +180,14 @@
179
180
  "@types/pg": "8.15",
180
181
  "concurrently": "9.2",
181
182
  "drizzle-kit": "0.31",
182
- "eslint": "9.34",
183
- "globals": "16.3",
183
+ "eslint": "9.35",
184
+ "globals": "16.4",
184
185
  "tsc-alias": "1.8",
185
186
  "typedoc-github-wiki-theme": "2.1",
186
187
  "typedoc-plugin-markdown": "4.8",
187
188
  "typedoc-plugin-missing-exports": "4.1",
188
189
  "typescript": "5.9",
189
- "typescript-eslint": "8.42"
190
+ "typescript-eslint": "8.43"
190
191
  },
191
192
  "overrides": {
192
193
  "drizzle-kit": {
@@ -17,20 +17,35 @@ export declare function memoizeSingle<Fn extends (parameter: any) => any>(fn: Fn
17
17
  export declare function memoizeClass<T extends Constructor>(type: T, options?: MemoizeOptions): (...parameters: ConstructorParameters<T>) => InstanceType<T>;
18
18
  export declare function memoizeClassSingle<T extends Constructor<any, [any]>>(type: T, options?: MemoizeOptions): (...parameters: ConstructorParameters<T>) => InstanceType<T>;
19
19
  /**
20
- * Memoizes an accessor (getter)
20
+ * Memoizes the result of a class getter, ensuring the getter's logic is
21
+ * executed only once per instance.
22
+ *
23
+ * Subsequent accesses to the getter will return the cached value.
24
+ *
25
+ * @param strategy The memoization strategy to use.
26
+ * - `attach`: (Default) Uses `Symbol` properties to store the cached value directly on the class instance. This is generally faster but adds hidden properties to the instance.
27
+ * - `weak-map`: Uses a `WeakMap` to store the cached value, with the class instance as the key. This avoids adding any properties to the instance, keeping it "clean".
21
28
  *
22
29
  * @example
23
30
  * ```typescript
24
- * class MyClass {
31
+ * class MyEvaluator {
25
32
  * @Memoize()
26
- * get myValue() {
27
- * // expensive calculation
28
- * return 123;
33
+ * get result() {
34
+ * // This expensive result creation only happens once per instance.
35
+ * return this.computeExpensiveResult();
29
36
  * }
30
37
  * }
38
+ *
39
+ * const service = new MyEvaluator();
40
+ * const s1 = service.result; // Logs "Creating new result..."
41
+ * const s2 = service.result; // Does not log anything, returns cached result.
42
+ * console.log(s1 === s2); // true
31
43
  * ```
32
44
  *
33
45
  * @remarks
34
- * The getter will be called only once for each instance of the class.
46
+ * If the decorated property has a corresponding setter, calling the setter will
47
+ * invalidate the cache. The getter will be re-evaluated on its next access.
48
+ *
49
+ * @returns An accessor decorator function.
35
50
  */
36
- export declare function Memoize(): MethodDecorator;
51
+ export declare function Memoize(strategy?: 'attach' | 'weak-map'): MethodDecorator;
@@ -1,7 +1,8 @@
1
1
  import { IterableWeakMap } from '../../data-structures/iterable-weak-map.js';
2
2
  import { MultiKeyMap } from '../../data-structures/multi-key-map.js';
3
3
  import { createAccessorDecorator } from '../../reflection/index.js';
4
- import { assertDefinedPass, isDefined } from '../type-guards.js';
4
+ import { assertDefinedPass, isDefined } from '../../utils/type-guards.js';
5
+ import { hasOwnProperty } from '../object/object.js';
5
6
  /**
6
7
  * Memoizes a function with an arbitrary number of parameters. If you only need a single parameter, {@link memoizeSingle} is faster
7
8
  * @param fn function memoize
@@ -46,47 +47,105 @@ export function memoizeClassSingle(type, options = {}) {
46
47
  return memoizeSingle((parameter) => new type(parameter), options);
47
48
  }
48
49
  /**
49
- * Memoizes an accessor (getter)
50
+ * Memoizes the result of a class getter, ensuring the getter's logic is
51
+ * executed only once per instance.
52
+ *
53
+ * Subsequent accesses to the getter will return the cached value.
54
+ *
55
+ * @param strategy The memoization strategy to use.
56
+ * - `attach`: (Default) Uses `Symbol` properties to store the cached value directly on the class instance. This is generally faster but adds hidden properties to the instance.
57
+ * - `weak-map`: Uses a `WeakMap` to store the cached value, with the class instance as the key. This avoids adding any properties to the instance, keeping it "clean".
50
58
  *
51
59
  * @example
52
60
  * ```typescript
53
- * class MyClass {
61
+ * class MyEvaluator {
54
62
  * @Memoize()
55
- * get myValue() {
56
- * // expensive calculation
57
- * return 123;
63
+ * get result() {
64
+ * // This expensive result creation only happens once per instance.
65
+ * return this.computeExpensiveResult();
58
66
  * }
59
67
  * }
68
+ *
69
+ * const service = new MyEvaluator();
70
+ * const s1 = service.result; // Logs "Creating new result..."
71
+ * const s2 = service.result; // Does not log anything, returns cached result.
72
+ * console.log(s1 === s2); // true
60
73
  * ```
61
74
  *
62
75
  * @remarks
63
- * The getter will be called only once for each instance of the class.
76
+ * If the decorated property has a corresponding setter, calling the setter will
77
+ * invalidate the cache. The getter will be re-evaluated on its next access.
78
+ *
79
+ * @returns An accessor decorator function.
64
80
  */
65
- export function Memoize() {
66
- const cache = new WeakMap();
81
+ export function Memoize(strategy = 'attach') {
67
82
  return createAccessorDecorator({
68
83
  handler: (data) => {
69
84
  const getter = assertDefinedPass(data.descriptor.get, 'Memoize requires an getter for accessors.'); // eslint-disable-line @typescript-eslint/unbound-method
70
85
  const setter = data.descriptor.set; // eslint-disable-line @typescript-eslint/unbound-method
71
- function cachedGetter() {
72
- if (cache.has(this)) {
73
- return cache.get(this); // eslint-disable-line @typescript-eslint/no-unsafe-return
74
- }
75
- const value = getter.call(this);
76
- cache.set(this, value);
77
- return value; // eslint-disable-line @typescript-eslint/no-unsafe-return
86
+ if (strategy == 'attach') {
87
+ return createAttachMemoizeHandler(data.propertyKey, getter, setter);
78
88
  }
79
- function cachedSetter(value) {
80
- setter?.call(this, value);
81
- cache.delete(this);
89
+ else {
90
+ return createWeakMapMemoizeHandler(getter, setter);
82
91
  }
83
- return {
84
- get: cachedGetter,
85
- set: isDefined(setter) ? cachedSetter : undefined,
86
- };
92
+ ;
87
93
  },
88
94
  });
89
95
  }
96
+ function createAttachMemoizeHandler(propertyKey, getter, setter) {
97
+ const stateKey = Symbol(`memoization[${String(propertyKey)}]`);
98
+ function getState(instance) {
99
+ if (!hasOwnProperty(instance, stateKey)) {
100
+ Object.defineProperty(instance, stateKey, {
101
+ value: { hasValue: false, value: undefined },
102
+ writable: false,
103
+ configurable: true,
104
+ enumerable: false,
105
+ });
106
+ }
107
+ return instance[stateKey];
108
+ }
109
+ function cachedGetter() {
110
+ const state = getState(this);
111
+ if (state.hasValue) {
112
+ return state.value;
113
+ }
114
+ const value = getter.call(this);
115
+ state.hasValue = true;
116
+ state.value = value;
117
+ return value;
118
+ }
119
+ function cachedSetter(value) {
120
+ setter.call(this, value);
121
+ if (hasOwnProperty(this, stateKey)) {
122
+ this[stateKey].hasValue = false;
123
+ this[stateKey].value = undefined;
124
+ }
125
+ }
126
+ return {
127
+ get: cachedGetter,
128
+ set: isDefined(setter) ? cachedSetter : undefined,
129
+ };
130
+ }
131
+ function createWeakMapMemoizeHandler(getter, setter) {
132
+ const cache = new WeakMap();
133
+ function cachedGetter() {
134
+ if (cache.has(this)) {
135
+ return cache.get(this);
136
+ }
137
+ const value = getter.call(this);
138
+ cache.set(this, value);
139
+ return value;
140
+ }
141
+ ;
142
+ function cachedSetter(value) {
143
+ setter.call(this, value);
144
+ cache.delete(this);
145
+ }
146
+ ;
147
+ return { get: cachedGetter, set: isDefined(setter) ? cachedSetter : undefined };
148
+ }
90
149
  function getMemoizedName(fn) {
91
150
  return `memoized${fn.name[0]?.toUpperCase() ?? ''}${fn.name.slice(1)}`;
92
151
  }