apimo.js 1.0.2 → 1.0.3
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/package.json +1 -1
- package/dist/src/core/api.test.d.ts +0 -1
- package/dist/src/core/api.test.js +0 -246
- package/dist/src/services/storage/dummy.cache.test.d.ts +0 -1
- package/dist/src/services/storage/dummy.cache.test.js +0 -96
- package/dist/src/services/storage/filesystem.cache.test.d.ts +0 -1
- package/dist/src/services/storage/filesystem.cache.test.js +0 -197
- package/dist/src/services/storage/memory.cache.test.d.ts +0 -1
- package/dist/src/services/storage/memory.cache.test.js +0 -80
- package/dist/src/utils/url.test.d.ts +0 -1
- package/dist/src/utils/url.test.js +0 -18
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -6
- /package/dist/{src/consts → consts}/catalogs.d.ts +0 -0
- /package/dist/{src/consts → consts}/catalogs.js +0 -0
- /package/dist/{src/consts → consts}/languages.d.ts +0 -0
- /package/dist/{src/consts → consts}/languages.js +0 -0
- /package/dist/{src/core → core}/api.d.ts +0 -0
- /package/dist/{src/core → core}/api.js +0 -0
- /package/dist/{src/core → core}/converters.d.ts +0 -0
- /package/dist/{src/core → core}/converters.js +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/index.js → index.js} +0 -0
- /package/dist/{src/schemas → schemas}/agency.d.ts +0 -0
- /package/dist/{src/schemas → schemas}/agency.js +0 -0
- /package/dist/{src/schemas → schemas}/common.d.ts +0 -0
- /package/dist/{src/schemas → schemas}/common.js +0 -0
- /package/dist/{src/schemas → schemas}/internal.d.ts +0 -0
- /package/dist/{src/schemas → schemas}/internal.js +0 -0
- /package/dist/{src/schemas → schemas}/property.d.ts +0 -0
- /package/dist/{src/schemas → schemas}/property.js +0 -0
- /package/dist/{src/services → services}/storage/dummy.cache.d.ts +0 -0
- /package/dist/{src/services → services}/storage/dummy.cache.js +0 -0
- /package/dist/{src/services → services}/storage/filesystem.cache.d.ts +0 -0
- /package/dist/{src/services → services}/storage/filesystem.cache.js +0 -0
- /package/dist/{src/services → services}/storage/memory.cache.d.ts +0 -0
- /package/dist/{src/services → services}/storage/memory.cache.js +0 -0
- /package/dist/{src/services → services}/storage/types.d.ts +0 -0
- /package/dist/{src/services → services}/storage/types.js +0 -0
- /package/dist/{src/types → types}/index.d.ts +0 -0
- /package/dist/{src/types → types}/index.js +0 -0
- /package/dist/{src/utils → utils}/url.d.ts +0 -0
- /package/dist/{src/utils → utils}/url.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apimo.js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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
|
-
});
|
package/dist/vitest.config.d.ts
DELETED
package/dist/vitest.config.js
DELETED
|
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
|
|
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
|