apimo.js 1.0.2 → 1.0.4

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.
Files changed (45) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +8 -0
  3. package/dist/{src/index.d.ts → index.d.ts} +3 -1
  4. package/dist/{src/schemas → schemas}/agency.d.ts +3 -0
  5. package/dist/{src/schemas → schemas}/common.d.ts +2 -0
  6. package/dist/{src/schemas → schemas}/property.d.ts +15 -0
  7. package/package.json +1 -1
  8. package/dist/src/core/api.test.d.ts +0 -1
  9. package/dist/src/core/api.test.js +0 -246
  10. package/dist/src/services/storage/dummy.cache.test.d.ts +0 -1
  11. package/dist/src/services/storage/dummy.cache.test.js +0 -96
  12. package/dist/src/services/storage/filesystem.cache.test.d.ts +0 -1
  13. package/dist/src/services/storage/filesystem.cache.test.js +0 -197
  14. package/dist/src/services/storage/memory.cache.test.d.ts +0 -1
  15. package/dist/src/services/storage/memory.cache.test.js +0 -80
  16. package/dist/src/utils/url.test.d.ts +0 -1
  17. package/dist/src/utils/url.test.js +0 -18
  18. package/dist/vitest.config.d.ts +0 -2
  19. package/dist/vitest.config.js +0 -6
  20. /package/dist/{src/consts → consts}/catalogs.d.ts +0 -0
  21. /package/dist/{src/consts → consts}/catalogs.js +0 -0
  22. /package/dist/{src/consts → consts}/languages.d.ts +0 -0
  23. /package/dist/{src/consts → consts}/languages.js +0 -0
  24. /package/dist/{src/core → core}/api.d.ts +0 -0
  25. /package/dist/{src/core → core}/api.js +0 -0
  26. /package/dist/{src/core → core}/converters.d.ts +0 -0
  27. /package/dist/{src/core → core}/converters.js +0 -0
  28. /package/dist/{src/index.js → index.js} +0 -0
  29. /package/dist/{src/schemas → schemas}/agency.js +0 -0
  30. /package/dist/{src/schemas → schemas}/common.js +0 -0
  31. /package/dist/{src/schemas → schemas}/internal.d.ts +0 -0
  32. /package/dist/{src/schemas → schemas}/internal.js +0 -0
  33. /package/dist/{src/schemas → schemas}/property.js +0 -0
  34. /package/dist/{src/services → services}/storage/dummy.cache.d.ts +0 -0
  35. /package/dist/{src/services → services}/storage/dummy.cache.js +0 -0
  36. /package/dist/{src/services → services}/storage/filesystem.cache.d.ts +0 -0
  37. /package/dist/{src/services → services}/storage/filesystem.cache.js +0 -0
  38. /package/dist/{src/services → services}/storage/memory.cache.d.ts +0 -0
  39. /package/dist/{src/services → services}/storage/memory.cache.js +0 -0
  40. /package/dist/{src/services → services}/storage/types.d.ts +0 -0
  41. /package/dist/{src/services → services}/storage/types.js +0 -0
  42. /package/dist/{src/types → types}/index.d.ts +0 -0
  43. /package/dist/{src/types → types}/index.js +0 -0
  44. /package/dist/{src/utils → utils}/url.d.ts +0 -0
  45. /package/dist/{src/utils → utils}/url.js +0 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Vitaly Lysen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -346,6 +346,14 @@ To use this library, you need:
346
346
 
347
347
  Contact [Apimo customer service](https://apimo.net/en/contact/) to request your credentials.
348
348
 
349
+ ## Currently Missing Features
350
+
351
+ - [ ] **Hardened error handling**: More robust error handling for API responses
352
+ - [ ] **Exported types & schemas**: Export all internal types & schemas for better TypeScript integration & validation
353
+ - [ ] **Minified build**: Smaller production build size
354
+ - [ ] **Documentation for all methods**: Complete API documentation for all available methods
355
+ - [ ] **More comprehensive tests**: Additional test coverage for edge cases
356
+
349
357
  ## Support
350
358
 
351
359
  - 📖 [Apimo API Documentation](https://apimo.net/en/api/webservice/)
@@ -2,7 +2,9 @@ export type { CatalogName } from './consts/catalogs';
2
2
  export type { ApiCulture } from './consts/languages';
3
3
  export { type AdditionalConfig, Apimo, DEFAULT_ADDITIONAL_CONFIG, DEFAULT_BASE_URL } from './core/api';
4
4
  export { Apimo as Api } from './core/api';
5
- export type { CatalogDefinition, CatalogEntry, CatalogTransformer, LocalizedCatalogTransformer } from './schemas/common';
5
+ export type { ApimoAgency, ApimoPartner, ApimoRate } from './schemas/agency';
6
+ export type { ApimoCity, ApimoUser, CatalogDefinition, CatalogEntry, CatalogTransformer, LocalizedCatalogTransformer, } from './schemas/common';
7
+ export type { ApimoAgreement, ApimoArea, ApimoComment, ApimoConstruction, ApimoFloor, ApimoHeating, ApimoPicture, ApimoPlot, ApimoPrice, ApimoProperty, ApimoRegulation, ApimoResidence, ApimoSurface, ApimoView, ApimoWater, } from './schemas/property';
6
8
  export { DummyCache } from './services/storage/dummy.cache';
7
9
  export { FilesystemCache } from './services/storage/filesystem.cache';
8
10
  export { MemoryCache } from './services/storage/memory.cache';
@@ -414,3 +414,6 @@ export declare function getAgencySchema(transformer: LocalizedCatalogTransformer
414
414
  brand?: unknown;
415
415
  district?: unknown;
416
416
  }>;
417
+ export type ApimoAgency = z.infer<ReturnType<typeof getAgencySchema>>;
418
+ export type ApimoPartner = z.infer<typeof PartnerSchema>;
419
+ export type ApimoRate = z.infer<ReturnType<typeof getRateSchema>>;
@@ -151,3 +151,5 @@ export declare function getUserSchema(transformer: LocalizedCatalogTransformer):
151
151
  stories?: unknown[] | undefined;
152
152
  rates?: unknown;
153
153
  }>;
154
+ export type ApimoUser = z.infer<ReturnType<typeof getUserSchema>>;
155
+ export type ApimoCity = z.infer<typeof CitySchema>;
@@ -1498,3 +1498,18 @@ export declare function getPropertySchema(transformer: LocalizedCatalogTransform
1498
1498
  interagency_comment?: unknown;
1499
1499
  status_comment?: unknown;
1500
1500
  }>;
1501
+ export type ApimoProperty = z.infer<ReturnType<typeof getPropertySchema>>;
1502
+ export type ApimoRegulation = z.infer<ReturnType<typeof getRegulationSchema>>;
1503
+ export type ApimoConstruction = z.infer<ReturnType<typeof getConstructionSchema>>;
1504
+ export type ApimoFloor = z.infer<ReturnType<typeof getFloorSchema>>;
1505
+ export type ApimoHeating = z.infer<ReturnType<typeof getHeatingSchema>>;
1506
+ export type ApimoWater = z.infer<ReturnType<typeof getWaterSchema>>;
1507
+ export type ApimoPrice = z.infer<ReturnType<typeof getPriceSchema>>;
1508
+ export type ApimoResidence = z.infer<ReturnType<typeof getResidenceSchema>>;
1509
+ export type ApimoView = z.infer<ReturnType<typeof getViewSchema>>;
1510
+ export type ApimoAgreement = z.infer<ReturnType<typeof getAgreementSchema>>;
1511
+ export type ApimoSurface = z.infer<ReturnType<typeof getSurfaceSchema>>;
1512
+ export type ApimoPlot = z.infer<ReturnType<typeof getPlotSchema>>;
1513
+ export type ApimoArea = z.infer<ReturnType<typeof getAreaSchema>>;
1514
+ export type ApimoPicture = z.infer<typeof PictureSchema>;
1515
+ export type ApimoComment = z.infer<typeof CommentSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apimo.js",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A wrapper for the Apimo API with catalog caching for building custom Real Estate website using their technologies.",
5
5
  "author": "Vitaly Lysen <vitaly@lysen.dev> (https://lysen.dev)",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- export {};
@@ -1,246 +0,0 @@
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, it as defaultIt, describe, expect, vi } from 'vitest';
11
- import { z } from 'zod';
12
- import { DummyCache } from '../services/storage/dummy.cache';
13
- import { MemoryCache } from '../services/storage/memory.cache';
14
- import { Apimo, DEFAULT_BASE_URL } from './api';
15
- // Mock fetch globally
16
- const mockFetch = vi.fn();
17
- const PROVIDER = '0';
18
- const TOKEN = 'TOKEN';
19
- const BasicAuthHeaders = {
20
- Authorization: `Basic ${btoa(`${PROVIDER}:${TOKEN}`)}`,
21
- };
22
- const it = defaultIt.extend({
23
- // eslint-disable-next-line no-empty-pattern
24
- api: (_a, use_1) => __awaiter(void 0, [_a, use_1], void 0, function* ({}, use) {
25
- let api = new Apimo('0', 'TOKEN', {
26
- catalogs: {
27
- transform: {
28
- active: false,
29
- },
30
- },
31
- });
32
- yield use(api);
33
- api = null;
34
- }),
35
- // eslint-disable-next-line no-empty-pattern
36
- mockResponse: (_a, use_1) => __awaiter(void 0, [_a, use_1], void 0, function* ({}, use) {
37
- const mockResponse = (config) => {
38
- var _a, _b;
39
- mockFetch.mockResolvedValue({
40
- ok: (_a = config === null || config === void 0 ? void 0 : config.ok) !== null && _a !== void 0 ? _a : true,
41
- status: (_b = config === null || config === void 0 ? void 0 : config.status) !== null && _b !== void 0 ? _b : 200,
42
- json: (config === null || config === void 0 ? void 0 : config.json) ? vi.fn().mockResolvedValue(config.json()) : vi.fn().mockResolvedValue({}),
43
- text: vi.fn(),
44
- headers: new Headers(),
45
- statusText: 'OK',
46
- url: '',
47
- redirected: false,
48
- type: 'basic',
49
- body: null,
50
- bodyUsed: false,
51
- clone: vi.fn(),
52
- arrayBuffer: vi.fn(),
53
- blob: vi.fn(),
54
- formData: vi.fn(),
55
- });
56
- };
57
- yield use(mockResponse);
58
- }),
59
- });
60
- describe('api', () => {
61
- let mockResponse;
62
- beforeEach(() => {
63
- // Mock global fetch
64
- vi.stubGlobal('fetch', mockFetch);
65
- // Create a mock response object
66
- mockResponse = {
67
- ok: true,
68
- status: 200,
69
- json: vi.fn(),
70
- text: vi.fn(),
71
- headers: new Headers(),
72
- statusText: 'OK',
73
- url: '',
74
- redirected: false,
75
- type: 'basic',
76
- body: null,
77
- bodyUsed: false,
78
- clone: vi.fn(),
79
- arrayBuffer: vi.fn(),
80
- blob: vi.fn(),
81
- formData: vi.fn(),
82
- };
83
- mockFetch.mockResolvedValue(mockResponse);
84
- });
85
- afterEach(() => {
86
- vi.unstubAllGlobals();
87
- vi.clearAllMocks();
88
- });
89
- describe('constructor', () => {
90
- it('should accept a provider, a token and a base config', ({ api }) => {
91
- expect(api).toBeInstanceOf(Apimo);
92
- });
93
- it('should use default config when no additional config provided', ({ api }) => {
94
- expect(api.config).toStrictEqual({
95
- baseUrl: DEFAULT_BASE_URL,
96
- culture: 'en',
97
- catalogs: {
98
- cache: {
99
- active: true,
100
- adapter: expect.any(MemoryCache),
101
- },
102
- transform: {
103
- active: false,
104
- },
105
- },
106
- });
107
- });
108
- it('should merge custom config with defaults', () => {
109
- const testApi = new Apimo('provider', 'token', {
110
- baseUrl: 'https://custom.api.com',
111
- culture: 'fr',
112
- catalogs: {
113
- cache: {
114
- active: false,
115
- adapter: new DummyCache(),
116
- },
117
- },
118
- });
119
- expect(testApi.config).toStrictEqual({
120
- baseUrl: 'https://custom.api.com',
121
- culture: 'fr',
122
- catalogs: {
123
- cache: {
124
- active: false,
125
- adapter: expect.any(DummyCache),
126
- },
127
- transform: {
128
- active: true,
129
- },
130
- },
131
- });
132
- });
133
- it('should use provided cache adapter', () => {
134
- const testApi = new Apimo('provider', 'token', {
135
- catalogs: {
136
- cache: {
137
- adapter: new DummyCache(),
138
- },
139
- },
140
- });
141
- expect(testApi.cache).toBeInstanceOf(DummyCache);
142
- });
143
- it('should use DummyCache when cache is not active', () => {
144
- const testApi = new Apimo('provider', 'token', {
145
- catalogs: {
146
- cache: {
147
- active: false,
148
- adapter: new MemoryCache(),
149
- },
150
- },
151
- });
152
- expect(testApi.cache).toBeInstanceOf(DummyCache);
153
- });
154
- });
155
- describe('fetch', () => {
156
- it('should have the right authorization headers when fetching', () => __awaiter(void 0, void 0, void 0, function* () {
157
- const testApi = new Apimo('provider', 'token');
158
- yield testApi.fetch(DEFAULT_BASE_URL);
159
- expect(mockFetch).toHaveBeenCalledExactlyOnceWith(DEFAULT_BASE_URL, {
160
- headers: {
161
- Authorization: `Basic ${btoa('provider:token')}`,
162
- },
163
- });
164
- }));
165
- it('should merge additional headers with authorization', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api }) {
166
- const customHeaders = { 'Content-Type': 'application/json' };
167
- yield api.fetch(DEFAULT_BASE_URL, { headers: customHeaders });
168
- expect(mockFetch).toHaveBeenCalledWith(DEFAULT_BASE_URL, {
169
- headers: Object.assign(Object.assign({}, BasicAuthHeaders), { 'Content-Type': 'application/json' }),
170
- });
171
- }));
172
- it('should pass through other fetch options', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api }) {
173
- const options = {
174
- method: 'POST',
175
- body: JSON.stringify({ test: 'data' }),
176
- headers: { 'Custom-Header': 'value' },
177
- };
178
- yield api.fetch(DEFAULT_BASE_URL, options);
179
- expect(mockFetch).toHaveBeenCalledWith(DEFAULT_BASE_URL, {
180
- method: 'POST',
181
- body: JSON.stringify({ test: 'data' }),
182
- headers: Object.assign(Object.assign({}, BasicAuthHeaders), { 'Custom-Header': 'value' }),
183
- });
184
- }));
185
- it('should handle rate limiting with Bottleneck', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api }) {
186
- // Make multiple concurrent requests to test rate limiting
187
- const promises = Array.from({ length: 3 }, () => api.fetch(DEFAULT_BASE_URL));
188
- yield Promise.all(promises);
189
- expect(mockFetch).toHaveBeenCalledTimes(3);
190
- }));
191
- });
192
- describe('get', () => {
193
- it('should fetch and parse according to the specified schema', (_a) => __awaiter(void 0, [_a], void 0, function* ({ mockResponse, api }) {
194
- mockResponse({
195
- json: () => ({ success: true }),
196
- });
197
- const spy = vi.spyOn(api, 'fetch');
198
- yield api.get(['path', 'to', 'catalogs'], z.object({ success: z.boolean() }), { culture: 'en' });
199
- expect(spy).toHaveBeenCalledExactlyOnceWith(new URL('https://api.apimo.pro/path/to/catalogs?culture=en'));
200
- }));
201
- });
202
- describe('populateCache', () => {
203
- it('should populate cache without returning entry when no id provided', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api, mockResponse }) {
204
- const catalogName = 'property_type';
205
- const culture = 'en';
206
- const mockEntries = [
207
- { id: 1, name: 'Apartment', name_plurial: 'Apartments' },
208
- { id: 2, name: 'House', name_plurial: 'Houses' },
209
- ];
210
- mockResponse({
211
- json: () => mockEntries,
212
- });
213
- const result = yield api.populateCache(catalogName, culture);
214
- expect(result).toBeUndefined();
215
- expect(mockFetch).toHaveBeenCalledExactlyOnceWith(new URL(`https://api.apimo.pro/catalogs/${catalogName}?culture=${culture}`), {
216
- headers: BasicAuthHeaders,
217
- });
218
- }));
219
- it('should populate cache and return specific entry when id provided', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api, mockResponse }) {
220
- const catalogName = 'property_type';
221
- const culture = 'en';
222
- const mockEntries = [
223
- { id: 1, name: 'Apartment', name_plurial: 'Apartments' },
224
- { id: 2, name: 'House', name_plurial: 'Houses' },
225
- ];
226
- mockResponse({
227
- json: () => mockEntries,
228
- });
229
- const result = yield api.populateCache(catalogName, culture, 1);
230
- expect(result).toEqual({
231
- name: 'Apartment',
232
- namePlural: 'Apartments',
233
- });
234
- }));
235
- it('should return null when requested id not found', (_a) => __awaiter(void 0, [_a], void 0, function* ({ api, mockResponse }) {
236
- const catalogName = 'property_type';
237
- const culture = 'en';
238
- const mockEntries = [
239
- { id: 1, name: 'Apartment', name_plurial: 'Apartments' },
240
- ];
241
- mockResponse({ json: () => mockEntries });
242
- const result = yield api.populateCache(catalogName, culture, 999);
243
- expect(result).toBeNull();
244
- }));
245
- });
246
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,96 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,197 +0,0 @@
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 { existsSync, mkdirSync, rmSync } from 'node:fs';
11
- import { readFile, writeFile } from 'node:fs/promises';
12
- import * as os from 'node:os';
13
- import * as path from 'node:path';
14
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
15
- import { FilesystemCache } from './filesystem.cache';
16
- import { CacheExpiredError } from './types';
17
- describe('cache - Filesystem', () => {
18
- let tempDir;
19
- let cache;
20
- beforeEach(() => {
21
- // Create a unique temporary directory for each test
22
- tempDir = path.join(os.tmpdir(), `filesystem-cache-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
23
- mkdirSync(tempDir, { recursive: true });
24
- });
25
- afterEach(() => {
26
- // Clean up the temporary directory after each test
27
- if (existsSync(tempDir)) {
28
- rmSync(tempDir, { recursive: true, force: true });
29
- }
30
- });
31
- describe('constructor', () => {
32
- it('should use default cache location and expiration when no settings provided', () => {
33
- // eslint-disable-next-line no-new
34
- new FilesystemCache();
35
- expect(existsSync('./cache/catalogs')).toBe(true);
36
- });
37
- it('should create custom cache directory when provided', () => {
38
- const customPath = path.join(tempDir, 'custom-cache');
39
- cache = new FilesystemCache({ path: customPath });
40
- expect(existsSync(customPath)).toBe(true);
41
- });
42
- it('should use custom expiration time when provided', () => {
43
- const customExpiration = 60000;
44
- cache = new FilesystemCache({
45
- path: tempDir,
46
- cacheExpirationMs: customExpiration,
47
- });
48
- // The expiration time is private, but we can test its effect
49
- expect(cache).toBeInstanceOf(FilesystemCache);
50
- });
51
- it('should create nested directories recursively', () => {
52
- const nestedPath = path.join(tempDir, 'level1', 'level2', 'cache');
53
- cache = new FilesystemCache({ path: nestedPath });
54
- expect(existsSync(nestedPath)).toBe(true);
55
- });
56
- });
57
- describe('setEntries and getEntry', () => {
58
- const culture = 'en';
59
- const entries = [
60
- { id: 1, name: 'Item 1', name_plurial: 'Items 1' },
61
- { id: 2, name: 'Item 2', name_plurial: 'Items 2' },
62
- ];
63
- beforeEach(() => {
64
- cache = new FilesystemCache({ path: tempDir });
65
- });
66
- it('should store and retrieve entries correctly', () => __awaiter(void 0, void 0, void 0, function* () {
67
- const catalogName = 'book_step';
68
- yield cache.setEntries(catalogName, culture, entries);
69
- const entry1 = yield cache.getEntry(catalogName, culture, 1);
70
- const entry2 = yield cache.getEntry(catalogName, culture, 2);
71
- expect(entry1).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
72
- expect(entry2).toEqual({ name: 'Item 2', namePlural: 'Items 2' });
73
- }));
74
- it('should create cache file with correct format', () => __awaiter(void 0, void 0, void 0, function* () {
75
- const catalogName = 'book_step';
76
- yield cache.setEntries(catalogName, culture, entries);
77
- const filePath = path.join(tempDir, `${catalogName}-${culture}.json`);
78
- expect(existsSync(filePath)).toBe(true);
79
- const fileContent = yield readFile(filePath, 'utf-8');
80
- const parsed = JSON.parse(fileContent);
81
- expect(parsed).toHaveProperty('timestamp');
82
- expect(parsed).toHaveProperty('cache');
83
- expect(parsed.cache).toEqual({
84
- 1: { name: 'Item 1', namePlural: 'Items 1' },
85
- 2: { name: 'Item 2', namePlural: 'Items 2' },
86
- });
87
- expect(typeof parsed.timestamp).toBe('number');
88
- }));
89
- it('should return null for non-existent entry', () => __awaiter(void 0, void 0, void 0, function* () {
90
- const catalogName = 'book_step';
91
- yield cache.setEntries(catalogName, culture, entries);
92
- const entry = yield cache.getEntry(catalogName, culture, 999);
93
- expect(entry).toBeNull();
94
- }));
95
- it('should throw CacheExpiredError when cache file does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
96
- const catalogName = 'book_step';
97
- yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
98
- }));
99
- it('should throw CacheExpiredError when cache has expired', () => __awaiter(void 0, void 0, void 0, function* () {
100
- const catalogName = 'book_step';
101
- const expiredCache = new FilesystemCache({
102
- path: tempDir,
103
- cacheExpirationMs: 1,
104
- });
105
- yield expiredCache.setEntries(catalogName, culture, entries);
106
- yield new Promise(resolve => setTimeout(resolve, 10));
107
- yield expect(expiredCache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError);
108
- }));
109
- it('should handle different catalog and culture combinations', () => __awaiter(void 0, void 0, void 0, function* () {
110
- yield cache.setEntries('book_step', 'en', entries);
111
- yield cache.setEntries('property_land', 'fr', entries);
112
- const entry1 = yield cache.getEntry('book_step', 'en', 1);
113
- const entry2 = yield cache.getEntry('property_land', 'fr', 1);
114
- expect(entry1).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
115
- expect(entry2).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
116
- yield expect(cache.getEntry('book_step', 'fr', 1)).rejects.toThrow(CacheExpiredError);
117
- // Verify separate files were created
118
- expect(existsSync(path.join(tempDir, 'book_step-en.json'))).toBe(true);
119
- expect(existsSync(path.join(tempDir, 'property_land-fr.json'))).toBe(true);
120
- expect(existsSync(path.join(tempDir, 'book_step-fr.json'))).toBe(false);
121
- }));
122
- it('should overwrite existing cache when setting new entries', () => __awaiter(void 0, void 0, void 0, function* () {
123
- const catalogName = 'property_land';
124
- yield cache.setEntries(catalogName, culture, entries);
125
- const newEntries = [{ id: 3, name: 'New Item', name_plurial: 'New Items' }];
126
- yield cache.setEntries(catalogName, culture, newEntries);
127
- const newEntry = yield cache.getEntry(catalogName, culture, 3);
128
- expect(newEntry).toEqual({ name: 'New Item', namePlural: 'New Items' });
129
- const oldEntry = yield cache.getEntry(catalogName, culture, 1);
130
- expect(oldEntry).toBeNull();
131
- }));
132
- it('should handle entries with undefined name_plurial', () => __awaiter(void 0, void 0, void 0, function* () {
133
- const catalogName = 'book_step';
134
- const entriesWithUndefined = [
135
- { id: 1, name: 'Item 1', name_plurial: undefined },
136
- { id: 2, name: 'Item 2', name_plurial: 'Items 2' },
137
- ];
138
- yield cache.setEntries(catalogName, culture, entriesWithUndefined);
139
- const entry1 = yield cache.getEntry(catalogName, culture, 1);
140
- const entry2 = yield cache.getEntry(catalogName, culture, 2);
141
- expect(entry1).toEqual({ name: 'Item 1', namePlural: undefined });
142
- expect(entry2).toEqual({ name: 'Item 2', namePlural: 'Items 2' });
143
- }));
144
- it('should handle large entry IDs correctly', () => __awaiter(void 0, void 0, void 0, function* () {
145
- const catalogName = 'book_step';
146
- const largeIdEntries = [
147
- { id: 999999999, name: 'Large ID Item', name_plurial: 'Large ID Items' },
148
- ];
149
- yield cache.setEntries(catalogName, culture, largeIdEntries);
150
- const entry = yield cache.getEntry(catalogName, culture, 999999999);
151
- expect(entry).toEqual({ name: 'Large ID Item', namePlural: 'Large ID Items' });
152
- }));
153
- it('should persist cache across multiple FilesystemCache instances', () => __awaiter(void 0, void 0, void 0, function* () {
154
- const catalogName = 'book_step';
155
- // Create cache with first instance
156
- const cache1 = new FilesystemCache({ path: tempDir });
157
- yield cache1.setEntries(catalogName, culture, entries);
158
- // Access cache with second instance
159
- const cache2 = new FilesystemCache({ path: tempDir });
160
- const entry = yield cache2.getEntry(catalogName, culture, 1);
161
- expect(entry).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
162
- }));
163
- });
164
- describe('error handling', () => {
165
- beforeEach(() => {
166
- cache = new FilesystemCache({ path: tempDir });
167
- });
168
- it('should handle malformed JSON files gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
169
- const catalogName = 'book_step';
170
- const culture = 'en';
171
- const filePath = path.join(tempDir, `${catalogName}-${culture}.json`);
172
- // Write malformed JSON
173
- yield writeFile(filePath, 'invalid json content');
174
- yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow();
175
- }));
176
- it('should handle files with missing timestamp', () => __awaiter(void 0, void 0, void 0, function* () {
177
- const catalogName = 'book_step';
178
- const culture = 'en';
179
- const filePath = path.join(tempDir, `${catalogName}-${culture}.json`);
180
- // Write JSON without timestamp
181
- yield writeFile(filePath, JSON.stringify({
182
- cache: { 1: { name: 'Test', namePlural: 'Tests' } },
183
- }));
184
- yield expect(cache.getEntry(catalogName, culture, 1)).resolves.toStrictEqual({ name: 'Test', namePlural: 'Tests' });
185
- }));
186
- it('should handle files with missing cache property', () => __awaiter(void 0, void 0, void 0, function* () {
187
- const catalogName = 'book_step';
188
- const culture = 'en';
189
- const filePath = path.join(tempDir, `${catalogName}-${culture}.json`);
190
- // Write JSON without cache property
191
- yield writeFile(filePath, JSON.stringify({
192
- timestamp: Date.now(),
193
- }));
194
- yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow();
195
- }));
196
- });
197
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,80 +0,0 @@
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 { describe, expect, it } from 'vitest';
11
- import { MemoryCache } from './memory.cache';
12
- describe('cache - Memory', () => {
13
- describe('constructor', () => {
14
- it('should use default expiration time when no settings provided', () => {
15
- const defaultCache = new MemoryCache();
16
- expect(defaultCache.cacheExpirationMs).toBe(7 * 24 * 60 * 60 * 1000);
17
- });
18
- it('should use custom expiration time when provided', () => {
19
- const customExpiration = 60000;
20
- const customCache = new MemoryCache({ cacheExpirationMs: customExpiration });
21
- expect(customCache.cacheExpirationMs).toBe(customExpiration);
22
- });
23
- });
24
- describe('setEntries and getEntry', () => {
25
- const culture = 'en';
26
- const entries = [
27
- { id: 1, name: 'Item 1', name_plurial: 'Items 1' },
28
- { id: 2, name: 'Item 2', name_plurial: 'Items 2' },
29
- ];
30
- it('should store and retrieve entries correctly', () => __awaiter(void 0, void 0, void 0, function* () {
31
- const cache = new MemoryCache();
32
- const catalogName = 'book_step';
33
- yield cache.setEntries(catalogName, culture, entries);
34
- const entry1 = yield cache.getEntry(catalogName, culture, 1);
35
- const entry2 = yield cache.getEntry(catalogName, culture, 2);
36
- expect(entry1).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
37
- expect(entry2).toEqual({ name: 'Item 2', namePlural: 'Items 2' });
38
- }));
39
- it('should return null for non-existent entry', () => __awaiter(void 0, void 0, void 0, function* () {
40
- const cache = new MemoryCache();
41
- const catalogName = 'book_step';
42
- yield cache.setEntries(catalogName, culture, entries);
43
- const entry = yield cache.getEntry(catalogName, culture, 999);
44
- expect(entry).toBeNull();
45
- }));
46
- it('should throw CacheExpiredError when cache entry does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
47
- const cache = new MemoryCache();
48
- const catalogName = 'book_step';
49
- yield expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrowError();
50
- }));
51
- it('should throw CacheExpiredError when cache has expired', () => __awaiter(void 0, void 0, void 0, function* () {
52
- const catalogName = 'book_step';
53
- const expiredCache = new MemoryCache({ cacheExpirationMs: 1 });
54
- yield expiredCache.setEntries(catalogName, culture, entries);
55
- yield new Promise(resolve => setTimeout(resolve, 10));
56
- yield expect(expiredCache.getEntry(catalogName, culture, 1)).rejects.toThrowError();
57
- }));
58
- it('should handle different catalog and culture combinations', () => __awaiter(void 0, void 0, void 0, function* () {
59
- const cache = new MemoryCache();
60
- yield cache.setEntries('book_step', 'en', entries);
61
- yield cache.setEntries('property_land', 'fr', entries);
62
- const entry1 = yield cache.getEntry('book_step', 'en', 1);
63
- const entry2 = yield cache.getEntry('property_land', 'fr', 1);
64
- expect(entry1).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
65
- expect(entry2).toEqual({ name: 'Item 1', namePlural: 'Items 1' });
66
- yield expect(cache.getEntry('book_step', 'fr', 1)).rejects.toThrowError();
67
- }));
68
- it('should overwrite existing cache when setting new entries', () => __awaiter(void 0, void 0, void 0, function* () {
69
- const cache = new MemoryCache();
70
- const catalogName = 'property_land';
71
- yield cache.setEntries(catalogName, culture, entries);
72
- const newEntries = [{ id: 3, name: 'New Item', name_plurial: 'New Items' }];
73
- yield cache.setEntries(catalogName, culture, newEntries);
74
- const newEntry = yield cache.getEntry(catalogName, culture, 3);
75
- expect(newEntry).toEqual({ name: 'New Item', namePlural: 'New Items' });
76
- const oldEntry = yield cache.getEntry(catalogName, culture, 1);
77
- expect(oldEntry).toBeNull();
78
- }));
79
- });
80
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,18 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { DEFAULT_ADDITIONAL_CONFIG } from '../core/api';
3
- import { makeApiUrl } from './url';
4
- const CONFIG = DEFAULT_ADDITIONAL_CONFIG;
5
- describe('url util', () => {
6
- it('should, given an array of parts return a complete url', () => {
7
- const url = makeApiUrl(['hello', 'world'], CONFIG);
8
- expect(url.href).toBe('https://api.apimo.pro/hello/world');
9
- });
10
- it('should, given an array of parts and search params return a complete url', () => {
11
- const url = makeApiUrl(['hello', 'world'], CONFIG, { culture: 'fr', limit: 10 });
12
- expect(url.href).toBe('https://api.apimo.pro/hello/world?culture=fr&limit=10');
13
- });
14
- it('should, given an array of parts and search params with undefined values, ignore them', () => {
15
- const url = makeApiUrl(['hello', 'world'], CONFIG, { culture: 'fr', limit: undefined });
16
- expect(url.href).toBe('https://api.apimo.pro/hello/world?culture=fr');
17
- });
18
- });
@@ -1,2 +0,0 @@
1
- declare const _default: import("vite").UserConfig;
2
- export default _default;
@@ -1,6 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
- export default defineConfig({
3
- test: {
4
- setupFiles: ['dotenv/config'],
5
- },
6
- });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes