@ez4/cache 0.42.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Silas B.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # EZ4: Cache
2
+
3
+ It uses the power of [reflection](../../foundation/reflection/) to provide a contract that determines how to build and connect cache components.
4
+
5
+ ## Getting started
6
+
7
+ #### Install
8
+
9
+ ```sh
10
+ npm install @ez4/cache @ez4/local-cache @ez4/aws-valkey -D
11
+ ```
12
+
13
+ #### Create cache
14
+
15
+ ```ts
16
+ // file: cache.ts
17
+ import type { Environment, Service } from '@ez4/common';
18
+ import type { Cache } from '@ez4/email';
19
+
20
+ // MyCache declaration
21
+ export declare class MyCache extends Cache.Service {
22
+ engine: Cache.UseEngine<{
23
+ name: 'valkey
24
+ }>
25
+ }
26
+ ```
27
+
28
+ #### Use cache
29
+
30
+ ```ts
31
+ // file: handler.ts
32
+ import type { Service } from '@ez4/common';
33
+ import type { MyCache } from './cache';
34
+
35
+ // Any other handler that has injected MyCache service
36
+ export async function anyHandler(_request: any, context: Service.Context<DummyService>) {
37
+ const { myCache } = context;
38
+
39
+ await myCache.set('key', 'value');
40
+
41
+ const value = myCache.get('key');
42
+ }
43
+ ```
44
+
45
+ ## Cache properties
46
+
47
+ #### Service
48
+
49
+ | Name | Type | Description |
50
+ | ------ | ----------------- | ---------------------------------------- |
51
+ | engine | Cache.UseEngine<> | Determines which database engine to use. |
52
+
53
+ ## Examples
54
+
55
+ - [Get started with cache](../../examples/hello-aws-cache)
56
+
57
+ ## Providers
58
+
59
+ - [AWS provider](../../providers/aws/aws-valkey)
60
+ - [Local provider](../../providers/local/local-cache)
61
+
62
+ ## License
63
+
64
+ MIT License
@@ -0,0 +1,11 @@
1
+ import { IncompleteTypeError, IncorrectTypeError, InvalidTypeError } from '@ez4/common/library';
2
+ export declare class IncompleteEngineError extends IncompleteTypeError {
3
+ constructor(properties: string[], fileName?: string);
4
+ }
5
+ export declare class InvalidEngineTypeError extends InvalidTypeError {
6
+ constructor(fileName?: string);
7
+ }
8
+ export declare class IncorrectEngineTypeError extends IncorrectTypeError {
9
+ engineType: string;
10
+ constructor(engineType: string, fileName?: string);
11
+ }
@@ -0,0 +1,4 @@
1
+ import { IncompleteTypeError } from '@ez4/common/library';
2
+ export declare class IncompleteServiceError extends IncompleteTypeError {
3
+ constructor(properties: string[], fileName?: string);
4
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";var h=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var n=(e,r)=>h(e,"name",{value:r,configurable:!0});var W=(e,r)=>{for(var t in r)h(e,t,{get:r[t],enumerable:!0})},q=(e,r,t,s)=>{if(r&&
2
+ typeof r=="object"||typeof r=="function")for(let i of L(r))!H.call(e,i)&&i!==t&&
3
+ h(e,i,{get:()=>r[i],enumerable:!(s=A(r,i))||s.enumerable});return e};var F=e=>q(h({},"__esModule",{value:!0}),e);var J={};W(J,{IncompleteEngineError:()=>d,IncompleteServiceError:()=>g,IncorrectEngineTypeError:()=>v,
4
+ InvalidEngineTypeError:()=>y,ServiceType:()=>u,createCacheService:()=>b,getCacheEngineMetadata:()=>S,
5
+ getCacheServicesMetadata:()=>x,getLinkedService:()=>E,isCacheEngineDeclaration:()=>z,
6
+ isCacheService:()=>V,isCacheServiceDeclaration:()=>T,registerTriggers:()=>G});module.
7
+ exports=F(J);var O=require("@ez4/common/library"),P=require("@ez4/project/library");var o=require("@ez4/common/library"),j=require("@ez4/reflection"),D=require("@ez4/utils");var M=require("@ez4/common/library");var g=class extends M.IncompleteTypeError{static{n(this,"IncompleteServiceError")}constructor(r,t){
8
+ super("Incomplete cache service",r,t)}};var f=require("@ez4/reflection"),w=require("@ez4/utils"),c=require("@ez4/common/library");var l=require("@ez4/common/library");var d=class extends l.IncompleteTypeError{static{n(this,"IncompleteEngineError")}constructor(r,t){
9
+ super("Incomplete cache engine",r,t)}},y=class extends l.InvalidTypeError{static{
10
+ n(this,"InvalidEngineTypeError")}constructor(r){super("Invalid cache engine type",
11
+ void 0,"Cache.Engine",r)}},v=class extends l.IncorrectTypeError{constructor(t,s){
12
+ super("Incorrect cache engine type",t,"Cache.Engine",s);this.engineType=t}static{
13
+ n(this,"IncorrectEngineTypeError")}};var z=n(e=>(0,c.hasHeritageType)(e,"Cache.Engine"),"isCacheEngineDeclaration"),S=n(
14
+ (e,r,t,s)=>{if(!(0,f.isTypeReference)(e))return I(e,r,s);let i=(0,c.getReferenceType)(
15
+ e,t);if(i)return I(i,r,s)},"getCacheEngineMetadata"),N=n(e=>(0,w.isObjectWith)(e,
16
+ ["name"]),"isCompleteEngine"),I=n((e,r,t)=>{if((0,f.isTypeObject)(e))return k(e,
17
+ r,(0,c.getObjectMembers)(e),t);if(!(0,c.isModelDeclaration)(e)){t.push(new y(r.file));
18
+ return}if(!z(e)){t.push(new v(e.name,e.file));return}return k(e,r,(0,c.getModelMembers)(
19
+ e),t)},"getTypeEngine"),k=n((e,r,t,s)=>{let i={},a=new Set(["name"]);for(let p of t)
20
+ if(!(!(0,f.isModelProperty)(p)||p.inherited))switch(p.name){default:{s.push(new c.InvalidServicePropertyError(
21
+ r.name,p.name,e.file));break}case"name":{(i.name=(0,c.getPropertyString)(p))&&a.
22
+ delete(p.name);break}}if(!N(i)){s.push(new d([...a],e.file));return}return i},"g\
23
+ etTypeFromMembers");var R=require("@ez4/project/library");var u="@ez4/cache",V=n(e=>e.type===u,"isCacheService"),b=n(e=>({...(0,R.createServiceMetadata)(
24
+ u,e),variables:{},services:{}}),"createCacheService");var T=n(e=>(0,o.isClassDeclaration)(e)&&(0,o.hasHeritageType)(e,"Cache.Service"),
25
+ "isCacheServiceDeclaration"),x=n(e=>{let r={},t=[];for(let s in e){let i=e[s];if(!T(
26
+ i)||(0,o.isExternalDeclaration)(i))continue;let a=b(i.name),p=new Set(["engine"]),
27
+ C=i.file;i.description&&(a.description=i.description);for(let m of(0,o.getModelMembers)(
28
+ i))if((0,j.isModelProperty)(m))switch(m.name){default:{t.push(new o.InvalidServicePropertyError(
29
+ a.name,m.name,C));break}case"client":break;case"engine":{(a.engine=S(m.value,i,e,
30
+ t))&&p.delete(m.name);break}case"variables":{a.variables=(0,o.getLinkedVariableList)(
31
+ m,t);break}case"services":{a.services=(0,o.getLinkedServiceList)(m,e,t);break}}if(!B(
32
+ a)){t.push(new g([...p],C));continue}if(r[i.name]){t.push(new o.DuplicateServiceError(
33
+ i.name,C));continue}r[i.name]=a}return{services:r,errors:t}},"getCacheServicesMe\
34
+ tadata"),B=n(e=>(0,D.isObjectWith)(e,["engine","variables","services"]),"isCompl\
35
+ eteService");var E=n(e=>T(e)?e.name:null,"getLinkedService");var G=n(()=>{(0,O.registerTriggers)(),(0,P.tryCreateTrigger)(u,{"metadata:getSer\
36
+ vices":x,"metadata:getLinkedService":E})},"registerTriggers");0&&(module.exports={IncompleteEngineError,IncompleteServiceError,IncorrectEngineTypeError,
37
+ InvalidEngineTypeError,ServiceType,createCacheService,getCacheEngineMetadata,getCacheServicesMetadata,
38
+ getLinkedService,isCacheEngineDeclaration,isCacheService,isCacheServiceDeclaration,
39
+ registerTriggers});
40
+ //# sourceMappingURL=library.cjs.map
@@ -0,0 +1,7 @@
1
+ export * from './triggers/register';
2
+ export * from './triggers/service';
3
+ export * from './errors/service';
4
+ export * from './errors/engine';
5
+ export * from './metadata/service';
6
+ export * from './metadata/engine';
7
+ export * from './metadata/types';
@@ -0,0 +1,33 @@
1
+ var b=Object.defineProperty;var r=(e,i)=>b(e,"name",{value:i,configurable:!0});import{registerTriggers as _}from"@ez4/common/library";import{tryCreateTrigger as $}from"@ez4/project/library";import{DuplicateServiceError as N,InvalidServicePropertyError as V,isExternalDeclaration as B,
2
+ isClassDeclaration as G,getLinkedServiceList as J,getLinkedVariableList as K,getModelMembers as Q,
3
+ hasHeritageType as U}from"@ez4/common/library";import{isModelProperty as X}from"@ez4/reflection";
4
+ import{isObjectWith as Y}from"@ez4/utils";import{IncompleteTypeError as x}from"@ez4/common/library";var p=class extends x{static{r(this,"IncompleteServiceError")}constructor(i,t){super(
5
+ "Incomplete cache service",i,t)}};import{isModelProperty as k,isTypeObject as w,isTypeReference as z}from"@ez4/reflection";
6
+ import{isObjectWith as R}from"@ez4/utils";import{InvalidServicePropertyError as j,
7
+ isModelDeclaration as D,getModelMembers as O,getObjectMembers as P,getPropertyString as A,
8
+ getReferenceType as L,hasHeritageType as H}from"@ez4/common/library";import{IncompleteTypeError as E,IncorrectTypeError as M,InvalidTypeError as I}from"@ez4/common/library";var m=class extends E{static{r(this,"IncompleteEngineError")}constructor(i,t){super(
9
+ "Incomplete cache engine",i,t)}},l=class extends I{static{r(this,"InvalidEngineT\
10
+ ypeError")}constructor(i){super("Invalid cache engine type",void 0,"Cache.Engine",
11
+ i)}},f=class extends M{constructor(t,c){super("Incorrect cache engine type",t,"C\
12
+ ache.Engine",c);this.engineType=t}static{r(this,"IncorrectEngineTypeError")}};var W=r(e=>H(e,"Cache.Engine"),"isCacheEngineDeclaration"),h=r((e,i,t,c)=>{if(!z(
13
+ e))return v(e,i,c);let n=L(e,t);if(n)return v(n,i,c)},"getCacheEngineMetadata"),
14
+ q=r(e=>R(e,["name"]),"isCompleteEngine"),v=r((e,i,t)=>{if(w(e))return u(e,i,P(e),
15
+ t);if(!D(e)){t.push(new l(i.file));return}if(!W(e)){t.push(new f(e.name,e.file));
16
+ return}return u(e,i,O(e),t)},"getTypeEngine"),u=r((e,i,t,c)=>{let n={},o=new Set(
17
+ ["name"]);for(let s of t)if(!(!k(s)||s.inherited))switch(s.name){default:{c.push(
18
+ new j(i.name,s.name,e.file));break}case"name":{(n.name=A(s))&&o.delete(s.name);break}}
19
+ if(!q(n)){c.push(new m([...o],e.file));return}return n},"getTypeFromMembers");import{createServiceMetadata as F}from"@ez4/project/library";var g="@ez4/cache",de=r(e=>e.type===g,"isCacheService"),T=r(e=>({...F(g,e),variables:{},
20
+ services:{}}),"createCacheService");var y=r(e=>G(e)&&U(e,"Cache.Service"),"isCacheServiceDeclaration"),C=r(e=>{let i={},
21
+ t=[];for(let c in e){let n=e[c];if(!y(n)||B(n))continue;let o=T(n.name),s=new Set(
22
+ ["engine"]),d=n.file;n.description&&(o.description=n.description);for(let a of Q(
23
+ n))if(X(a))switch(a.name){default:{t.push(new V(o.name,a.name,d));break}case"cli\
24
+ ent":break;case"engine":{(o.engine=h(a.value,n,e,t))&&s.delete(a.name);break}case"\
25
+ variables":{o.variables=K(a,t);break}case"services":{o.services=J(a,e,t);break}}
26
+ if(!Z(o)){t.push(new p([...s],d));continue}if(i[n.name]){t.push(new N(n.name,d));
27
+ continue}i[n.name]=o}return{services:i,errors:t}},"getCacheServicesMetadata"),Z=r(
28
+ e=>Y(e,["engine","variables","services"]),"isCompleteService");var S=r(e=>y(e)?e.name:null,"getLinkedService");var Oe=r(()=>{_(),$(g,{"metadata:getServices":C,"metadata:getLinkedService":S})},
29
+ "registerTriggers");export{m as IncompleteEngineError,p as IncompleteServiceError,f as IncorrectEngineTypeError,
30
+ l as InvalidEngineTypeError,g as ServiceType,T as createCacheService,h as getCacheEngineMetadata,
31
+ C as getCacheServicesMetadata,S as getLinkedService,W as isCacheEngineDeclaration,
32
+ de as isCacheService,y as isCacheServiceDeclaration,Oe as registerTriggers};
33
+ //# sourceMappingURL=library.mjs.map
package/dist/main.cjs ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";var o=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var a=Object.prototype.hasOwnProperty;var l=(n,e)=>{for(var t in e)o(n,t,{get:e[t],enumerable:!0})},s=(n,e,t,i)=>{if(e&&
2
+ typeof e=="object"||typeof e=="function")for(let r of c(e))!a.call(n,r)&&r!==t&&
3
+ o(n,r,{get:()=>e[r],enumerable:!(i=p(e,r))||i.enumerable});return n};var g=n=>s(o({},"__esModule",{value:!0}),n);var x={};l(x,{Cache:()=>m});module.exports=g(x);var m;(n=>{})(m||={});0&&(module.exports={Cache});
4
+ //# sourceMappingURL=main.cjs.map
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './services/contract';
2
+ export * from './services/client';
package/dist/main.mjs ADDED
@@ -0,0 +1,2 @@
1
+ var e;(n=>{})(e||={});export{e as Cache};
2
+ //# sourceMappingURL=main.mjs.map
@@ -0,0 +1,4 @@
1
+ import type { AllType, ReflectionTypes, TypeModel } from '@ez4/reflection';
2
+ import type { CacheEngine } from './types';
3
+ export declare const isCacheEngineDeclaration: (type: TypeModel) => boolean;
4
+ export declare const getCacheEngineMetadata: (type: AllType, parent: TypeModel, reflection: ReflectionTypes, errorList: Error[]) => CacheEngine | undefined;
@@ -0,0 +1,7 @@
1
+ import type { AllType, ReflectionTypes, TypeClass } from '@ez4/reflection';
2
+ import type { CacheService } from './types';
3
+ export declare const isCacheServiceDeclaration: (type: AllType) => type is TypeClass;
4
+ export declare const getCacheServicesMetadata: (reflection: ReflectionTypes) => {
5
+ services: Record<string, CacheService>;
6
+ errors: Error[];
7
+ };
@@ -0,0 +1,20 @@
1
+ import type { ServiceMetadata } from '@ez4/project/library';
2
+ export declare const ServiceType = "@ez4/cache";
3
+ export type CacheService = Omit<ServiceMetadata, 'variables' | 'services'> & Required<Pick<ServiceMetadata, 'variables' | 'services'>> & {
4
+ type: typeof ServiceType;
5
+ description?: string;
6
+ engine: CacheEngine;
7
+ };
8
+ export type CacheEngine = {
9
+ name: string;
10
+ };
11
+ export declare const isCacheService: (service: ServiceMetadata) => service is CacheService;
12
+ export declare const createCacheService: (name: string) => {
13
+ variables: {};
14
+ services: {};
15
+ type: "@ez4/cache";
16
+ name: string;
17
+ context: Record<string, import("@ez4/project/library").LinkedContext>;
18
+ description?: string | null | undefined;
19
+ engine?: CacheEngine | null | undefined;
20
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cache client.
3
+ */
4
+ export interface Client {
5
+ /**
6
+ * Clear the entire cache removing all its keys.
7
+ */
8
+ flush(): Promise<void>;
9
+ /**
10
+ * Get the value corresponding to the given key.
11
+ *
12
+ * @param key Key name.
13
+ * @return Returns the corresponding key value or `undefined`.
14
+ */
15
+ get(key: string): Promise<string | undefined>;
16
+ /**
17
+ * Set the given value into the given key.
18
+ *
19
+ * @param key Key name.
20
+ * @param value Key value.
21
+ * @param options Options when setting the key.
22
+ */
23
+ set(key: string, value: string, options?: SetOptions): Promise<void>;
24
+ /**
25
+ * Set the expiration TTL for the given key.
26
+ *
27
+ * @param key Key name.
28
+ * @param ttl TTL for the key (in seconds).
29
+ * @returns Returns the `true` when the TTL is applied, `false` otherwise.
30
+ */
31
+ setTTL(key: string, ttl: number): Promise<boolean>;
32
+ /**
33
+ * Get the current TTL for the given key.
34
+ *
35
+ * @param key Key name.
36
+ * @returns Returns the current TTL or `undefined` when there's none.
37
+ */
38
+ getTTL(key: string): Promise<number | undefined>;
39
+ /**
40
+ * Rename the given key.
41
+ *
42
+ * @param key Key name.
43
+ * @param newkey New key name.
44
+ * @returns Returns `true` when the key is renamed, `false` otherwise.
45
+ */
46
+ rename(key: string, newkey: string): Promise<boolean>;
47
+ /**
48
+ * Delete all the given keys.
49
+ *
50
+ * @param keys Key names.
51
+ * @returns Returns the number of deleted keys.
52
+ */
53
+ delete(...keys: string[]): Promise<number>;
54
+ /**
55
+ * Determines whether the given keys exists.
56
+ *
57
+ * @param keys Key names.
58
+ * @returns Returns the number of existing keys.
59
+ */
60
+ exists(...keys: string[]): Promise<number>;
61
+ /**
62
+ * Increment the current value for the given key.
63
+ *
64
+ * @param key Key name.
65
+ * @param value Optional increment value. (Default is `1`)
66
+ * @returns Returns the final value.
67
+ */
68
+ increment(key: string, value?: number): Promise<number>;
69
+ /**
70
+ * Decrement the current value for the given key.
71
+ *
72
+ * @param key Key name.
73
+ * @param value Optional decrement value. (Default is `1`)
74
+ * @returns Returns the final value.
75
+ */
76
+ decrement(key: string, value?: number): Promise<number>;
77
+ }
78
+ /**
79
+ * Options for setting keys.
80
+ */
81
+ export type SetOptions = {
82
+ /**
83
+ * TTL for the key.
84
+ */
85
+ ttl?: number;
86
+ };
@@ -0,0 +1,27 @@
1
+ import type { Service as CommonService } from '@ez4/common';
2
+ import type { CacheEngine } from './engine';
3
+ import type { Client } from './client';
4
+ /**
5
+ * Provide all contracts for a self-managed cache service.
6
+ */
7
+ export declare namespace Cache {
8
+ type Engine = CacheEngine;
9
+ /**
10
+ * Cache Engine definition.
11
+ */
12
+ type UseEngine<T extends CacheEngine> = T;
13
+ /**
14
+ * Cache service.
15
+ */
16
+ abstract class Service implements CommonService.Provider {
17
+ /**
18
+ * Determines which cache engine to use.
19
+ * Check the provider package to know all the possible values.
20
+ */
21
+ abstract readonly engine: Engine;
22
+ /**
23
+ * Service client.
24
+ */
25
+ readonly client: Client;
26
+ }
27
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cache engine.
3
+ */
4
+ export type CacheEngine = {
5
+ name: string;
6
+ };
@@ -0,0 +1 @@
1
+ export declare const registerTriggers: () => void;
@@ -0,0 +1,2 @@
1
+ import type { TypeClass } from '@ez4/reflection';
2
+ export declare const getLinkedService: (declaration: TypeClass) => string | null;
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@ez4/cache",
3
+ "description": "EZ4: Components to build cache services",
4
+ "version": "0.42.0",
5
+ "author": "Silas B.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "keywords": [
9
+ "ez4"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/sbalmt/ez4.git",
14
+ "directory": "contracts/cache"
15
+ },
16
+ "engines": {
17
+ "node": ">=22.7"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/main.d.ts",
22
+ "require": "./dist/main.cjs",
23
+ "import": "./dist/main.mjs"
24
+ },
25
+ "./library": {
26
+ "types": "./dist/library.d.ts",
27
+ "require": "./dist/library.cjs",
28
+ "import": "./dist/library.mjs"
29
+ }
30
+ },
31
+ "sideEffects": [
32
+ "./dist/library.cjs",
33
+ "./dist/library.mjs"
34
+ ],
35
+ "workspaces": [
36
+ "foundation/*",
37
+ "contracts/*"
38
+ ],
39
+ "scripts": {
40
+ "lint": "eslint --cache",
41
+ "clean": "rm -rf dist/* *.tsbuildinfo .eslintcache",
42
+ "build": "tsc && node tools/bundler.mjs",
43
+ "test": "npm run build && tsc -p tsconfig.test.json && ez4 test",
44
+ "local:publish": "npm run build && npm run clean:registry && npm publish --registry http://localhost:4873",
45
+ "clean:registry": "rm -rf ../../.registry/@ez4/cache",
46
+ "live:publish": "npm run build && npm publish --access public"
47
+ },
48
+ "dependencies": {
49
+ "@ez4/common": "^0.42.0",
50
+ "@ez4/project": "^0.42.0",
51
+ "@ez4/reflection": "^0.42.0",
52
+ "@ez4/utils": "^0.42.0"
53
+ }
54
+ }