apimo.js 1.0.1
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/.github/workflows/ci.yml +37 -0
- package/.github/workflows/publish.yml +69 -0
- package/.idea/apimo.js.iml +13 -0
- package/.idea/copilotDiffState.xml +43 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +6 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +91 -0
- package/dist/src/consts/catalogs.d.ts +2 -0
- package/dist/src/consts/catalogs.js +53 -0
- package/dist/src/consts/languages.d.ts +2 -0
- package/dist/src/consts/languages.js +20 -0
- package/dist/src/core/api.d.ts +389 -0
- package/dist/src/core/api.js +157 -0
- package/dist/src/core/api.test.d.ts +1 -0
- package/dist/src/core/api.test.js +246 -0
- package/dist/src/core/converters.d.ts +4 -0
- package/dist/src/core/converters.js +4 -0
- package/dist/src/schemas/agency.d.ts +416 -0
- package/dist/src/schemas/agency.js +61 -0
- package/dist/src/schemas/common.d.ts +153 -0
- package/dist/src/schemas/common.js +47 -0
- package/dist/src/schemas/internal.d.ts +3 -0
- package/dist/src/schemas/internal.js +11 -0
- package/dist/src/schemas/property.d.ts +1500 -0
- package/dist/src/schemas/property.js +238 -0
- package/dist/src/services/storage/dummy.cache.d.ts +10 -0
- package/dist/src/services/storage/dummy.cache.js +28 -0
- package/dist/src/services/storage/dummy.cache.test.d.ts +1 -0
- package/dist/src/services/storage/dummy.cache.test.js +96 -0
- package/dist/src/services/storage/filesystem.cache.d.ts +18 -0
- package/dist/src/services/storage/filesystem.cache.js +85 -0
- package/dist/src/services/storage/filesystem.cache.test.d.ts +1 -0
- package/dist/src/services/storage/filesystem.cache.test.js +197 -0
- package/dist/src/services/storage/memory.cache.d.ts +20 -0
- package/dist/src/services/storage/memory.cache.js +62 -0
- package/dist/src/services/storage/memory.cache.test.d.ts +1 -0
- package/dist/src/services/storage/memory.cache.test.js +80 -0
- package/dist/src/services/storage/types.d.ts +16 -0
- package/dist/src/services/storage/types.js +4 -0
- package/dist/src/types/index.d.ts +4 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/utils/url.d.ts +14 -0
- package/dist/src/utils/url.js +11 -0
- package/dist/src/utils/url.test.d.ts +1 -0
- package/dist/src/utils/url.test.js +18 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +6 -0
- package/eslint.config.mjs +3 -0
- package/package.json +45 -0
- package/src/consts/catalogs.ts +55 -0
- package/src/consts/languages.ts +22 -0
- package/src/core/api.test.ts +308 -0
- package/src/core/api.ts +230 -0
- package/src/core/converters.ts +7 -0
- package/src/schemas/agency.ts +66 -0
- package/src/schemas/common.ts +67 -0
- package/src/schemas/internal.ts +13 -0
- package/src/schemas/property.ts +257 -0
- package/src/services/storage/dummy.cache.test.ts +110 -0
- package/src/services/storage/dummy.cache.ts +21 -0
- package/src/services/storage/filesystem.cache.test.ts +243 -0
- package/src/services/storage/filesystem.cache.ts +94 -0
- package/src/services/storage/memory.cache.test.ts +94 -0
- package/src/services/storage/memory.cache.ts +69 -0
- package/src/services/storage/types.ts +20 -0
- package/src/types/index.ts +5 -0
- package/src/utils/url.test.ts +21 -0
- package/src/utils/url.ts +27 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Converters } from '../core/converters';
|
|
3
|
+
import { CitySchema, getUserSchema, NameIdPairSchema } from './common';
|
|
4
|
+
import { TYPE_UNDOCUMENTED, TYPE_UNDOCUMENTED_NULLABLE } from './internal';
|
|
5
|
+
export function getAgreementSchema(transformer) {
|
|
6
|
+
return z.object({
|
|
7
|
+
type: z.coerce.number().transform(v => transformer('property_agreement', v)),
|
|
8
|
+
reference: z.string(),
|
|
9
|
+
start_at: z.coerce.string().transform(Converters.toDate),
|
|
10
|
+
end_at: z.coerce.string().transform(Converters.toDate),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function getSurfaceSchema(transformer) {
|
|
14
|
+
return z.object({
|
|
15
|
+
unit: z.coerce.number().transform(v => transformer('unit_area', v)),
|
|
16
|
+
value: z.coerce.number(),
|
|
17
|
+
total: z.coerce.number(),
|
|
18
|
+
weighted: z.coerce.number(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function getPlotSchema(transformer) {
|
|
22
|
+
return z.object({
|
|
23
|
+
net_floor: z.coerce.number(),
|
|
24
|
+
land_type: z.coerce.number().transform(v => transformer('property_land', v)),
|
|
25
|
+
width: z.coerce.number(),
|
|
26
|
+
height: z.coerce.number().optional(),
|
|
27
|
+
serviced_plot: z.boolean(),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function getPriceSchema(transformer) {
|
|
31
|
+
return z.object({
|
|
32
|
+
value: z.coerce.number(),
|
|
33
|
+
max: z.coerce.number(),
|
|
34
|
+
fees: z.coerce.number(),
|
|
35
|
+
unit: TYPE_UNDOCUMENTED_NULLABLE,
|
|
36
|
+
period: z.coerce.number().transform(v => transformer('property_period', v)),
|
|
37
|
+
hide: z.coerce.boolean(),
|
|
38
|
+
inventory: z.number().nullable(),
|
|
39
|
+
deposit: z.number().nullable(),
|
|
40
|
+
currency: z.string().toLowerCase(),
|
|
41
|
+
commission: z.number().nullable(),
|
|
42
|
+
transfer_tax: TYPE_UNDOCUMENTED_NULLABLE,
|
|
43
|
+
contribution: TYPE_UNDOCUMENTED_NULLABLE,
|
|
44
|
+
pension: TYPE_UNDOCUMENTED_NULLABLE,
|
|
45
|
+
tenant: z.number().nullable(),
|
|
46
|
+
vat: z.boolean().nullable(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function getResidenceSchema(transformer) {
|
|
50
|
+
return z.object({
|
|
51
|
+
id: z.coerce.number(),
|
|
52
|
+
type: z.coerce.number().transform(v => transformer('property_building', v)),
|
|
53
|
+
fees: z.coerce.number(),
|
|
54
|
+
period: z.coerce.number().transform(v => transformer('property_period', v)),
|
|
55
|
+
lots: z.coerce.number(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function getViewSchema(transformer) {
|
|
59
|
+
return z.object({
|
|
60
|
+
type: z.coerce.number().transform(v => transformer('property_view_type', v)),
|
|
61
|
+
landscape: z.coerce.number().transform(v => transformer('property_view_landscape', v)).array(),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function getConstructionSchema(transformer) {
|
|
65
|
+
return z.object({
|
|
66
|
+
type: z.coerce.number().transform(v => transformer('property_construction_method', v)).array().optional(),
|
|
67
|
+
construction_year: z.coerce.number(),
|
|
68
|
+
renovation_year: z.coerce.number(),
|
|
69
|
+
renovation_cost: z.coerce.number(),
|
|
70
|
+
construction_step: z.coerce.number().transform(v => transformer('construction_step', v)),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function getFloorSchema(transformer) {
|
|
74
|
+
return z.object({
|
|
75
|
+
type: z.coerce.number().transform(v => transformer('property_floor', v)),
|
|
76
|
+
value: z.coerce.number(),
|
|
77
|
+
levels: z.coerce.number(),
|
|
78
|
+
floors: z.coerce.number(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function getHeatingSchema(transformer) {
|
|
82
|
+
return z.object({
|
|
83
|
+
device: z.coerce.number().transform(v => transformer('property_heating_device', v)),
|
|
84
|
+
devices: z.coerce.number().transform(v => transformer('property_heating_device', v)).array().nullable(),
|
|
85
|
+
access: z.coerce.number().transform(v => transformer('property_heating_access', v)),
|
|
86
|
+
type: z.coerce.number().transform(v => transformer('property_heating_type', v)),
|
|
87
|
+
types: z.coerce.number().transform(v => transformer('property_heating_type', v)).array().nullable(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function getWaterSchema(transformer) {
|
|
91
|
+
return z.object({
|
|
92
|
+
hot_device: z.coerce.number().transform(v => transformer('property_hot_water_device', v)),
|
|
93
|
+
hot_access: z.coerce.number().transform(v => transformer('property_hot_water_access', v)),
|
|
94
|
+
waste: z.coerce.number().transform(v => transformer('property_waste_water', v)),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export const CommentSchema = z.object({
|
|
98
|
+
language: z.string(),
|
|
99
|
+
title: z.string().optional().nullable(),
|
|
100
|
+
subtitle: z.string().optional().nullable(),
|
|
101
|
+
hook: TYPE_UNDOCUMENTED_NULLABLE.optional(),
|
|
102
|
+
comment: z.string(),
|
|
103
|
+
comment_full: z.string().optional().nullable(),
|
|
104
|
+
});
|
|
105
|
+
export const PictureSchema = z.object({
|
|
106
|
+
id: z.coerce.number(),
|
|
107
|
+
rank: z.coerce.number(),
|
|
108
|
+
url: z.string(),
|
|
109
|
+
width_max: z.coerce.number(),
|
|
110
|
+
height_max: z.coerce.number(),
|
|
111
|
+
internet: z.coerce.boolean(),
|
|
112
|
+
print: z.coerce.boolean(),
|
|
113
|
+
panorama: z.coerce.boolean(),
|
|
114
|
+
child: z.coerce.number(),
|
|
115
|
+
reference: TYPE_UNDOCUMENTED_NULLABLE,
|
|
116
|
+
comments: CommentSchema.array(),
|
|
117
|
+
});
|
|
118
|
+
export function getAreaSchema(transformer) {
|
|
119
|
+
return z.object({
|
|
120
|
+
type: z.coerce.number().transform(v => transformer('property_areas', v)),
|
|
121
|
+
number: z.coerce.number(),
|
|
122
|
+
area: z.coerce.number(),
|
|
123
|
+
flooring: z.coerce.number().transform(v => transformer('property_flooring', v)),
|
|
124
|
+
ceiling_height: z.number().nullable(),
|
|
125
|
+
floor: z.object({
|
|
126
|
+
type: z.coerce.number().transform(v => transformer('property_floor', v)),
|
|
127
|
+
value: z.coerce.number(),
|
|
128
|
+
}),
|
|
129
|
+
orientations: z.coerce.number().transform(v => transformer('property_orientation', v)).array(),
|
|
130
|
+
comments: CommentSchema.array(),
|
|
131
|
+
lot: z.object({
|
|
132
|
+
type: TYPE_UNDOCUMENTED_NULLABLE,
|
|
133
|
+
rank: TYPE_UNDOCUMENTED_NULLABLE,
|
|
134
|
+
name: TYPE_UNDOCUMENTED.array(),
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export function getRegulationSchema(transformer) {
|
|
139
|
+
return z.object({
|
|
140
|
+
type: z.coerce.number().transform(v => transformer('property_regulation', v)),
|
|
141
|
+
value: z.coerce.string().transform((v) => {
|
|
142
|
+
const values = v.split(',');
|
|
143
|
+
return values.map(value => Number.parseInt(value));
|
|
144
|
+
}),
|
|
145
|
+
date: z.string().transform(Converters.toDate).nullable(),
|
|
146
|
+
graph: z.string().nullable(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function getPropertySchema(transformer) {
|
|
150
|
+
return z.object({
|
|
151
|
+
id: z.coerce.number(),
|
|
152
|
+
reference: z.number(),
|
|
153
|
+
agency: z.coerce.number(),
|
|
154
|
+
brand: TYPE_UNDOCUMENTED_NULLABLE,
|
|
155
|
+
sector: TYPE_UNDOCUMENTED_NULLABLE,
|
|
156
|
+
user: getUserSchema(transformer),
|
|
157
|
+
step: z.number().transform(v => transformer('property_step', v)),
|
|
158
|
+
status: z.number().transform(v => transformer('property_status', v)),
|
|
159
|
+
parent: z.number().nullable(),
|
|
160
|
+
ranking: TYPE_UNDOCUMENTED_NULLABLE,
|
|
161
|
+
category: z.coerce.number().transform(v => transformer('property_category', v)),
|
|
162
|
+
name: z.string().nullable(),
|
|
163
|
+
type: z.coerce.number().transform(v => transformer('property_type', v)),
|
|
164
|
+
subtype: z.coerce.number().transform(v => transformer('property_subtype', v)),
|
|
165
|
+
agreement: getAgreementSchema(transformer).nullable(),
|
|
166
|
+
block_name: z.string().nullable(),
|
|
167
|
+
lot_reference: z.string().nullable(),
|
|
168
|
+
cadastre_reference: z.string().nullable(),
|
|
169
|
+
stairs_reference: z.string().nullable(),
|
|
170
|
+
address: z.string().nullable(),
|
|
171
|
+
address_more: z.string().nullable(),
|
|
172
|
+
publish_address: z.coerce.boolean(),
|
|
173
|
+
country: z.string().toLowerCase(),
|
|
174
|
+
region: NameIdPairSchema,
|
|
175
|
+
city: CitySchema,
|
|
176
|
+
original_city: TYPE_UNDOCUMENTED_NULLABLE,
|
|
177
|
+
district: NameIdPairSchema.nullable(),
|
|
178
|
+
original_district: TYPE_UNDOCUMENTED_NULLABLE,
|
|
179
|
+
location: TYPE_UNDOCUMENTED_NULLABLE,
|
|
180
|
+
longitude: z.coerce.number(),
|
|
181
|
+
latitude: z.coerce.number(),
|
|
182
|
+
radius: z.coerce.number(),
|
|
183
|
+
altitude: z.coerce.number(),
|
|
184
|
+
referral: TYPE_UNDOCUMENTED_NULLABLE,
|
|
185
|
+
subreferral: TYPE_UNDOCUMENTED_NULLABLE,
|
|
186
|
+
area: getSurfaceSchema(transformer),
|
|
187
|
+
plot: getPlotSchema(transformer),
|
|
188
|
+
rooms: z.coerce.number(),
|
|
189
|
+
bedrooms: z.coerce.number(),
|
|
190
|
+
sleeps: z.coerce.number(),
|
|
191
|
+
price: getPriceSchema(transformer),
|
|
192
|
+
rates: z.unknown().array(),
|
|
193
|
+
owner: TYPE_UNDOCUMENTED_NULLABLE,
|
|
194
|
+
visit: TYPE_UNDOCUMENTED_NULLABLE,
|
|
195
|
+
residence: getResidenceSchema(transformer).nullable(),
|
|
196
|
+
view: getViewSchema(transformer).nullable(),
|
|
197
|
+
construction: getConstructionSchema(transformer),
|
|
198
|
+
floor: getFloorSchema(transformer),
|
|
199
|
+
heating: getHeatingSchema(transformer),
|
|
200
|
+
water: getWaterSchema(transformer),
|
|
201
|
+
condition: z.coerce.number().transform(v => transformer('property_condition', v)),
|
|
202
|
+
standing: z.coerce.number().transform(v => transformer('property_standing', v)),
|
|
203
|
+
style: z.object({ name: z.string().nullable() }),
|
|
204
|
+
twinned: z.coerce.number().nullable(),
|
|
205
|
+
facades: z.coerce.number(),
|
|
206
|
+
length: z.coerce.number().nullable(),
|
|
207
|
+
height: z.coerce.number().nullable(),
|
|
208
|
+
url: z.string().nullable(),
|
|
209
|
+
availability: z.coerce.number().transform(v => transformer('property_availability', v)),
|
|
210
|
+
available_at: TYPE_UNDOCUMENTED_NULLABLE,
|
|
211
|
+
delivered_at: z.string().transform(Converters.toDate).nullable(),
|
|
212
|
+
activities: z.coerce.number().transform(v => transformer('property_activity', v)).array(),
|
|
213
|
+
orientations: z.coerce.number().transform(v => transformer('property_orientation', v)).array(),
|
|
214
|
+
services: z.coerce.number().transform(v => transformer('property_service', v)).array(),
|
|
215
|
+
proximities: z.coerce.number().transform(v => transformer('property_proximity', v)).array(),
|
|
216
|
+
tags: z.coerce.number().transform(v => transformer('tags', v)).array(),
|
|
217
|
+
tags_customized: z.unknown().array(),
|
|
218
|
+
pictures: PictureSchema.array(),
|
|
219
|
+
medias: z.unknown().array(),
|
|
220
|
+
documents: z.unknown().array(),
|
|
221
|
+
comments: CommentSchema.array(),
|
|
222
|
+
areas: getAreaSchema(transformer).array(),
|
|
223
|
+
regulations: getRegulationSchema(transformer).array(),
|
|
224
|
+
financial: z.unknown().array(),
|
|
225
|
+
exchanges: z.unknown().array(),
|
|
226
|
+
options: z.unknown().array(),
|
|
227
|
+
filling_rate: TYPE_UNDOCUMENTED_NULLABLE,
|
|
228
|
+
private_comment: TYPE_UNDOCUMENTED_NULLABLE,
|
|
229
|
+
interagency_comment: TYPE_UNDOCUMENTED_NULLABLE,
|
|
230
|
+
status_comment: TYPE_UNDOCUMENTED_NULLABLE,
|
|
231
|
+
logs: z.unknown().array(),
|
|
232
|
+
referrals: z.unknown().array(),
|
|
233
|
+
created_at: z.string().transform(Converters.toDate),
|
|
234
|
+
updated_at: z.string().transform(Converters.toDate),
|
|
235
|
+
created_by: z.coerce.number(),
|
|
236
|
+
updated_by: z.coerce.number(),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CatalogName } from '../../consts/catalogs';
|
|
2
|
+
import type { ApiCulture } from '../../consts/languages';
|
|
3
|
+
import type { CatalogEntry } from '../../schemas/common';
|
|
4
|
+
import type { ApiCacheAdapter, CatalogEntryName } from './types';
|
|
5
|
+
export declare class DummyCache implements ApiCacheAdapter {
|
|
6
|
+
constructor();
|
|
7
|
+
getEntries(_catalogName: CatalogName, _culture: ApiCulture): Promise<CatalogEntry[]>;
|
|
8
|
+
setEntries(_catalogName: CatalogName, _culture: ApiCulture, _entries: CatalogEntry[]): Promise<void>;
|
|
9
|
+
getEntry(_catalogName: CatalogName, _culture: ApiCulture, _id: number): Promise<CatalogEntryName | null>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { CacheExpiredError } from './types';
|
|
11
|
+
export class DummyCache {
|
|
12
|
+
constructor() {
|
|
13
|
+
}
|
|
14
|
+
getEntries(_catalogName, _culture) {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
throw new CacheExpiredError();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
setEntries(_catalogName, _culture, _entries) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
getEntry(_catalogName, _culture, _id) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
throw new CacheExpiredError();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
+
import { DummyCache } from './dummy.cache';
|
|
12
|
+
import { CacheExpiredError } from './types';
|
|
13
|
+
describe('cache - Dummy', () => {
|
|
14
|
+
let cache;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
cache = new DummyCache();
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// No cleanup needed for dummy cache
|
|
20
|
+
});
|
|
21
|
+
describe('constructor', () => {
|
|
22
|
+
it('should create an instance without any configuration', () => {
|
|
23
|
+
const dummyCache = new DummyCache();
|
|
24
|
+
expect(dummyCache).toBeInstanceOf(DummyCache);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('setEntries', () => {
|
|
28
|
+
const culture = 'en';
|
|
29
|
+
const entries = [
|
|
30
|
+
{ id: 1, name: 'Item 1', name_plurial: 'Items 1' },
|
|
31
|
+
{ id: 2, name: 'Item 2', name_plurial: 'Items 2' },
|
|
32
|
+
];
|
|
33
|
+
it('should not throw when setting entries', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
const catalogName = 'book_step';
|
|
35
|
+
yield expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined();
|
|
36
|
+
}));
|
|
37
|
+
it('should handle empty entries array', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
const catalogName = 'book_step';
|
|
39
|
+
yield expect(cache.setEntries(catalogName, culture, [])).resolves.toBeUndefined();
|
|
40
|
+
}));
|
|
41
|
+
it('should handle different catalog and culture combinations', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
yield expect(cache.setEntries('book_step', 'en', entries)).resolves.toBeUndefined();
|
|
43
|
+
yield expect(cache.setEntries('property_land', 'fr', entries)).resolves.toBeUndefined();
|
|
44
|
+
yield expect(cache.setEntries('property_type', 'de', entries)).resolves.toBeUndefined();
|
|
45
|
+
}));
|
|
46
|
+
});
|
|
47
|
+
describe('getEntry', () => {
|
|
48
|
+
const culture = 'en';
|
|
49
|
+
it('should always throw CacheExpiredError regardless of parameters', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
const catalogName = 'book_step';
|
|
51
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
52
|
+
}));
|
|
53
|
+
it('should throw CacheExpiredError for any ID', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const catalogName = 'book_step';
|
|
55
|
+
yield expect(cache.getEntry(catalogName, culture, 999)).rejects.toThrow(CacheExpiredError);
|
|
56
|
+
yield expect(cache.getEntry(catalogName, culture, 0)).rejects.toThrow(CacheExpiredError);
|
|
57
|
+
yield expect(cache.getEntry(catalogName, culture, -1)).rejects.toThrow(CacheExpiredError);
|
|
58
|
+
}));
|
|
59
|
+
it('should throw CacheExpiredError for different catalogs and cultures', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
yield expect(cache.getEntry('book_step', 'en', 1)).rejects.toThrow(CacheExpiredError);
|
|
61
|
+
yield expect(cache.getEntry('property_land', 'fr', 1)).rejects.toThrow(CacheExpiredError);
|
|
62
|
+
yield expect(cache.getEntry('property_type', 'de', 1)).rejects.toThrow(CacheExpiredError);
|
|
63
|
+
}));
|
|
64
|
+
it('should throw CacheExpiredError even after setting entries', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
const catalogName = 'book_step';
|
|
66
|
+
const entries = [{ id: 1, name: 'Item 1', name_plurial: 'Items 1' }];
|
|
67
|
+
yield cache.setEntries(catalogName, culture, entries);
|
|
68
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
describe('behavior consistency', () => {
|
|
72
|
+
it('should behave consistently across multiple calls', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
const catalogName = 'book_step';
|
|
74
|
+
const culture = 'en';
|
|
75
|
+
const entries = [{ id: 1, name: 'Item 1', name_plurial: 'Items 1' }];
|
|
76
|
+
// Multiple setEntries calls should not throw
|
|
77
|
+
yield expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined();
|
|
78
|
+
yield expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined();
|
|
79
|
+
// Multiple getEntry calls should always throw
|
|
80
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
81
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
82
|
+
}));
|
|
83
|
+
it('should maintain dummy behavior regardless of cache state', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
const catalogName = 'book_step';
|
|
85
|
+
const culture = 'en';
|
|
86
|
+
// Should throw before any operations
|
|
87
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
88
|
+
// Should still throw after setting entries
|
|
89
|
+
yield cache.setEntries(catalogName, culture, [{ id: 1, name: 'Test', name_plurial: 'Tests' }]);
|
|
90
|
+
yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
|
|
91
|
+
// Should still throw after multiple operations
|
|
92
|
+
yield cache.setEntries(catalogName, culture, []);
|
|
93
|
+
yield expect(cache.getEntry(catalogName, culture, 999)).rejects.toThrow(CacheExpiredError);
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CatalogName } from '../../consts/catalogs';
|
|
2
|
+
import type { ApiCulture } from '../../consts/languages';
|
|
3
|
+
import type { CatalogEntry } from '../../schemas/common';
|
|
4
|
+
import type { ApiCacheAdapter, CatalogEntryName } from './types';
|
|
5
|
+
export declare class FilesystemCache implements ApiCacheAdapter {
|
|
6
|
+
private readonly path;
|
|
7
|
+
private readonly cacheExpirationMs;
|
|
8
|
+
constructor(settings?: {
|
|
9
|
+
path?: string;
|
|
10
|
+
cacheExpirationMs?: number;
|
|
11
|
+
});
|
|
12
|
+
setEntries(catalogName: CatalogName, culture: ApiCulture, entries: CatalogEntry[]): Promise<void>;
|
|
13
|
+
readFileOrThrow(filePath: string): Promise<string>;
|
|
14
|
+
getEntry(catalogName: CatalogName, culture: ApiCulture, id: number): Promise<CatalogEntryName | null>;
|
|
15
|
+
getEntries(catalogName: CatalogName, culture: ApiCulture): Promise<CatalogEntry[]>;
|
|
16
|
+
private getCacheFilePath;
|
|
17
|
+
private getCacheFileName;
|
|
18
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { mkdirSync } from 'node:fs';
|
|
11
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { CacheExpiredError } from './types';
|
|
14
|
+
const DEFAULT_FILESYSTEM_CACHE_LOCATION = './cache/catalogs';
|
|
15
|
+
const MS_IN_ONE_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
export class FilesystemCache {
|
|
17
|
+
constructor(settings) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
this.path = (_a = settings === null || settings === void 0 ? void 0 : settings.path) !== null && _a !== void 0 ? _a : DEFAULT_FILESYSTEM_CACHE_LOCATION;
|
|
20
|
+
this.cacheExpirationMs = (_b = settings === null || settings === void 0 ? void 0 : settings.cacheExpirationMs) !== null && _b !== void 0 ? _b : MS_IN_ONE_WEEK;
|
|
21
|
+
mkdirSync(this.path, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
setEntries(catalogName, culture, entries) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const filePath = this.getCacheFilePath(catalogName, culture);
|
|
26
|
+
const formattedEntries = Object.fromEntries(entries.map(({ id, name, name_plurial }) => [id.toString(), {
|
|
27
|
+
name,
|
|
28
|
+
namePlural: name_plurial,
|
|
29
|
+
}]));
|
|
30
|
+
const dump = JSON.stringify({
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
cache: formattedEntries,
|
|
33
|
+
});
|
|
34
|
+
return writeFile(filePath, dump);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
readFileOrThrow(filePath) {
|
|
38
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
try {
|
|
40
|
+
return yield readFile(filePath, 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch (_a) {
|
|
43
|
+
throw new CacheExpiredError();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
getEntry(catalogName, culture, id) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
var _a;
|
|
50
|
+
const filePath = this.getCacheFilePath(catalogName, culture);
|
|
51
|
+
const data = yield this.readFileOrThrow(filePath);
|
|
52
|
+
const parsed = JSON.parse(data);
|
|
53
|
+
const currentTimestamp = Date.now();
|
|
54
|
+
if (parsed.timestamp + this.cacheExpirationMs < currentTimestamp) {
|
|
55
|
+
throw new CacheExpiredError();
|
|
56
|
+
}
|
|
57
|
+
return (_a = parsed.cache[id.toString()]) !== null && _a !== void 0 ? _a : null;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
getEntries(catalogName, culture) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const filePath = this.getCacheFilePath(catalogName, culture);
|
|
63
|
+
const data = yield this.readFileOrThrow(filePath);
|
|
64
|
+
const parsed = JSON.parse(data);
|
|
65
|
+
const currentTimestamp = Date.now();
|
|
66
|
+
if (parsed.timestamp + this.cacheExpirationMs < currentTimestamp) {
|
|
67
|
+
throw new CacheExpiredError();
|
|
68
|
+
}
|
|
69
|
+
return Object.entries(parsed.cache).map(([id, entry]) => {
|
|
70
|
+
var _a;
|
|
71
|
+
return ({
|
|
72
|
+
id: Number.parseInt(id, 10),
|
|
73
|
+
name: (_a = entry === null || entry === void 0 ? void 0 : entry.name) !== null && _a !== void 0 ? _a : 'missing',
|
|
74
|
+
name_plurial: entry === null || entry === void 0 ? void 0 : entry.namePlural,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
getCacheFilePath(catalogName, culture) {
|
|
80
|
+
return path.join(this.path, this.getCacheFileName(catalogName, culture));
|
|
81
|
+
}
|
|
82
|
+
getCacheFileName(catalogName, culture) {
|
|
83
|
+
return `${catalogName}-${culture}.json`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|