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.
Files changed (73) hide show
  1. package/.github/workflows/ci.yml +37 -0
  2. package/.github/workflows/publish.yml +69 -0
  3. package/.idea/apimo.js.iml +13 -0
  4. package/.idea/copilotDiffState.xml +43 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. package/.idea/jsLinters/eslint.xml +6 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/prettier.xml +6 -0
  9. package/.idea/vcs.xml +6 -0
  10. package/README.md +91 -0
  11. package/dist/src/consts/catalogs.d.ts +2 -0
  12. package/dist/src/consts/catalogs.js +53 -0
  13. package/dist/src/consts/languages.d.ts +2 -0
  14. package/dist/src/consts/languages.js +20 -0
  15. package/dist/src/core/api.d.ts +389 -0
  16. package/dist/src/core/api.js +157 -0
  17. package/dist/src/core/api.test.d.ts +1 -0
  18. package/dist/src/core/api.test.js +246 -0
  19. package/dist/src/core/converters.d.ts +4 -0
  20. package/dist/src/core/converters.js +4 -0
  21. package/dist/src/schemas/agency.d.ts +416 -0
  22. package/dist/src/schemas/agency.js +61 -0
  23. package/dist/src/schemas/common.d.ts +153 -0
  24. package/dist/src/schemas/common.js +47 -0
  25. package/dist/src/schemas/internal.d.ts +3 -0
  26. package/dist/src/schemas/internal.js +11 -0
  27. package/dist/src/schemas/property.d.ts +1500 -0
  28. package/dist/src/schemas/property.js +238 -0
  29. package/dist/src/services/storage/dummy.cache.d.ts +10 -0
  30. package/dist/src/services/storage/dummy.cache.js +28 -0
  31. package/dist/src/services/storage/dummy.cache.test.d.ts +1 -0
  32. package/dist/src/services/storage/dummy.cache.test.js +96 -0
  33. package/dist/src/services/storage/filesystem.cache.d.ts +18 -0
  34. package/dist/src/services/storage/filesystem.cache.js +85 -0
  35. package/dist/src/services/storage/filesystem.cache.test.d.ts +1 -0
  36. package/dist/src/services/storage/filesystem.cache.test.js +197 -0
  37. package/dist/src/services/storage/memory.cache.d.ts +20 -0
  38. package/dist/src/services/storage/memory.cache.js +62 -0
  39. package/dist/src/services/storage/memory.cache.test.d.ts +1 -0
  40. package/dist/src/services/storage/memory.cache.test.js +80 -0
  41. package/dist/src/services/storage/types.d.ts +16 -0
  42. package/dist/src/services/storage/types.js +4 -0
  43. package/dist/src/types/index.d.ts +4 -0
  44. package/dist/src/types/index.js +1 -0
  45. package/dist/src/utils/url.d.ts +14 -0
  46. package/dist/src/utils/url.js +11 -0
  47. package/dist/src/utils/url.test.d.ts +1 -0
  48. package/dist/src/utils/url.test.js +18 -0
  49. package/dist/vitest.config.d.ts +2 -0
  50. package/dist/vitest.config.js +6 -0
  51. package/eslint.config.mjs +3 -0
  52. package/package.json +45 -0
  53. package/src/consts/catalogs.ts +55 -0
  54. package/src/consts/languages.ts +22 -0
  55. package/src/core/api.test.ts +308 -0
  56. package/src/core/api.ts +230 -0
  57. package/src/core/converters.ts +7 -0
  58. package/src/schemas/agency.ts +66 -0
  59. package/src/schemas/common.ts +67 -0
  60. package/src/schemas/internal.ts +13 -0
  61. package/src/schemas/property.ts +257 -0
  62. package/src/services/storage/dummy.cache.test.ts +110 -0
  63. package/src/services/storage/dummy.cache.ts +21 -0
  64. package/src/services/storage/filesystem.cache.test.ts +243 -0
  65. package/src/services/storage/filesystem.cache.ts +94 -0
  66. package/src/services/storage/memory.cache.test.ts +94 -0
  67. package/src/services/storage/memory.cache.ts +69 -0
  68. package/src/services/storage/types.ts +20 -0
  69. package/src/types/index.ts +5 -0
  70. package/src/utils/url.test.ts +21 -0
  71. package/src/utils/url.ts +27 -0
  72. package/tsconfig.json +13 -0
  73. package/vitest.config.ts +7 -0
@@ -0,0 +1,37 @@
1
+ name: Continuous Integration
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ test-and-lint:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [20, 22, 24]
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ cache: yarn
26
+
27
+ - name: Install dependencies
28
+ run: yarn install --frozen-lockfile
29
+
30
+ - name: Run linting
31
+ run: yarn lint
32
+
33
+ - name: Run tests
34
+ run: yarn test
35
+
36
+ - name: Run test coverage
37
+ run: yarn test-coverage
@@ -0,0 +1,69 @@
1
+ name: Build, Test & Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: '22'
21
+ cache: yarn
22
+
23
+ - name: Install dependencies
24
+ run: yarn install --frozen-lockfile
25
+
26
+ - name: Run linting
27
+ run: yarn lint
28
+
29
+ - name: Run tests
30
+ run: yarn test
31
+
32
+ - name: Run test coverage
33
+ run: yarn test-coverage
34
+
35
+ build-and-publish:
36
+ needs: test
37
+ runs-on: ubuntu-latest
38
+
39
+ environment: Deploy
40
+
41
+ steps:
42
+ - name: Checkout code
43
+ uses: actions/checkout@v4
44
+
45
+ - name: Setup Node.js
46
+ uses: actions/setup-node@v4
47
+ with:
48
+ node-version: '22'
49
+ cache: yarn
50
+ registry-url: 'https://registry.npmjs.org'
51
+
52
+ - name: Install dependencies
53
+ run: yarn install --frozen-lockfile
54
+
55
+ - name: Build package
56
+ run: yarn build
57
+
58
+ - name: Check if dist directory exists
59
+ run: |
60
+ if [ ! -d "dist" ]; then
61
+ echo "Build failed: dist directory not found"
62
+ exit 1
63
+ fi
64
+ echo "Build successful: dist directory created"
65
+
66
+ - name: Publish to NPM
67
+ run: npm publish
68
+ env:
69
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ <excludeFolder url="file://$MODULE_DIR$/dist" />
9
+ </content>
10
+ <orderEntry type="inheritedJdk" />
11
+ <orderEntry type="sourceFolder" forTests="false" />
12
+ </component>
13
+ </module>
@@ -0,0 +1,43 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="CopilotDiffPersistence">
4
+ <option name="pendingDiffs">
5
+ <map>
6
+ <entry key="$PROJECT_DIR$/.github/workflows/ci.yml">
7
+ <value>
8
+ <PendingDiffInfo>
9
+ <option name="filePath" value="$PROJECT_DIR$/.github/workflows/ci.yml" />
10
+ <option name="updatedContent" value="name: Continuous Integration&#10;&#10;on:&#10; push:&#10; branches: [ main, develop ]&#10; pull_request:&#10; branches: [ main, develop ]&#10;&#10;jobs:&#10; test-and-lint:&#10; runs-on: ubuntu-latest&#10;&#10; strategy:&#10; matrix:&#10; node-version: [18, 20, 22]&#10;&#10; steps:&#10; - name: Checkout code&#10; uses: actions/checkout@v4&#10;&#10; - name: Setup Node.js ${{ matrix.node-version }}&#10; uses: actions/setup-node@v4&#10; with:&#10; node-version: ${{ matrix.node-version }}&#10; cache: 'yarn'&#10;&#10; - name: Install dependencies&#10; run: yarn install --frozen-lockfile&#10;&#10; - name: Run linting&#10; run: yarn lint&#10;&#10; - name: Run tests&#10; run: yarn test&#10;&#10; - name: Run test coverage&#10; run: yarn test-coverage&#10;&#10; - name: Upload coverage reports to Codecov&#10; uses: codecov/codecov-action@v4&#10; if: matrix.node-version == 20&#10; with:&#10; file: ./coverage/lcov.info&#10; fail_ci_if_error: false&#10; env:&#10; CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}" />
11
+ </PendingDiffInfo>
12
+ </value>
13
+ </entry>
14
+ <entry key="$PROJECT_DIR$/.github/workflows/publish.yml">
15
+ <value>
16
+ <PendingDiffInfo>
17
+ <option name="filePath" value="$PROJECT_DIR$/.github/workflows/publish.yml" />
18
+ <option name="updatedContent" value="name: Build, Test &amp; Publish to NPM&#10;&#10;on:&#10; push:&#10; tags:&#10; - 'v*' # Triggers on version tags like v1.0.0, v1.2.3, etc.&#10; workflow_dispatch: # Allows manual triggering&#10;&#10;jobs:&#10; test:&#10; runs-on: ubuntu-latest&#10; &#10; steps:&#10; - name: Checkout code&#10; uses: actions/checkout@v4&#10; &#10; - name: Setup Node.js&#10; uses: actions/setup-node@v4&#10; with:&#10; node-version: '18'&#10; cache: 'yarn'&#10; &#10; - name: Install dependencies&#10; run: yarn install --frozen-lockfile&#10; &#10; - name: Run linting&#10; run: yarn lint&#10; &#10; - name: Run tests&#10; run: yarn test&#10; &#10; - name: Run test coverage&#10; run: yarn test-coverage&#10;&#10; build-and-publish:&#10; needs: test&#10; runs-on: ubuntu-latest&#10; &#10; steps:&#10; - name: Checkout code&#10; uses: actions/checkout@v4&#10; &#10; - name: Setup Node.js&#10; uses: actions/setup-node@v4&#10; with:&#10; node-version: '18'&#10; cache: 'yarn'&#10; registry-url: 'https://registry.npmjs.org'&#10; &#10; - name: Install dependencies&#10; run: yarn install --frozen-lockfile&#10; &#10; - name: Build package&#10; run: yarn build&#10; &#10; - name: Check if dist directory exists&#10; run: |&#10; if [ ! -d &quot;dist&quot; ]; then&#10; echo &quot;Build failed: dist directory not found&quot;&#10; exit 1&#10; fi&#10; echo &quot;Build successful: dist directory created&quot;&#10; &#10; - name: Publish to NPM&#10; run: npm publish&#10; env:&#10; NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}" />
19
+ </PendingDiffInfo>
20
+ </value>
21
+ </entry>
22
+ <entry key="$PROJECT_DIR$/package.json">
23
+ <value>
24
+ <PendingDiffInfo>
25
+ <option name="filePath" value="$PROJECT_DIR$/package.json" />
26
+ <option name="originalContent" value="{&#10; &quot;name&quot;: &quot;apimo.js&quot;,&#10; &quot;version&quot;: &quot;1.0.1&quot;,&#10; &quot;description&quot;: &quot;A wrapper for the Apimo API with catalog caching for building custom Real Estate website using their technologies.&quot;,&#10; &quot;main&quot;: &quot;/dist/index.js&quot;,&#10; &quot;repository&quot;: {&#10; &quot;type&quot;: &quot;git&quot;,&#10; &quot;url&quot;: &quot;https://github.com/Neikow/apimo.js.git&quot;&#10; },&#10; &quot;homepage&quot;: &quot;https://github.com/Neikow/apimo.js&quot;,&#10; &quot;bugs&quot;: {&#10; &quot;url&quot;: &quot;https://github.com/Neikow/apimo.js/issues&quot;&#10; },&#10; &quot;keywords&quot;: [&#10; &quot;api&quot;,&#10; &quot;apimo&quot;,&#10; &quot;real-estate&quot;&#10; ],&#10; &quot;scripts&quot;: {&#10; &quot;dev&quot;: &quot;&quot;,&#10; &quot;build&quot;: &quot;tsc&quot;,&#10; &quot;run&quot;: &quot;node dist/index.js&quot;,&#10; &quot;test&quot;: &quot;vitest run&quot;,&#10; &quot;test-coverage&quot;: &quot;vitest run --coverage&quot;&#10; },&#10; &quot;author&quot;: &quot;Vitaly Lysen &lt;vitaly@lysen.dev&gt; (https://lysen.dev)&quot;,&#10; &quot;license&quot;: &quot;MIT&quot;,&#10; &quot;dependencies&quot;: {&#10; &quot;bottleneck&quot;: &quot;^2.19.5&quot;,&#10; &quot;merge-anything&quot;: &quot;^6.0.6&quot;,&#10; &quot;zod&quot;: &quot;^3.21.4&quot;&#10; },&#10; &quot;devDependencies&quot;: {&#10; &quot;@antfu/eslint-config&quot;: &quot;^5.0.0&quot;,&#10; &quot;@types/node&quot;: &quot;^24.1.0&quot;,&#10; &quot;@anatine/zod-mock&quot;: &quot;^3.14.0&quot;,&#10; &quot;@faker-js/faker&quot;: &quot;^9.9.0&quot;,&#10; &quot;@vitest/coverage-v8&quot;: &quot;^3.2.4&quot;,&#10; &quot;dotenv&quot;: &quot;^17.2.1&quot;,&#10; &quot;eslint&quot;: &quot;^9.32.0&quot;,&#10; &quot;typescript&quot;: &quot;^5.9.2&quot;,&#10; &quot;vitest&quot;: &quot;^3.2.4&quot;&#10; }&#10;}&#10;" />
27
+ <option name="updatedContent" value="{&#10; &quot;name&quot;: &quot;apimo.js&quot;,&#10; &quot;version&quot;: &quot;1.0.1&quot;,&#10; &quot;description&quot;: &quot;A wrapper for the Apimo API with catalog caching for building custom Real Estate website using their technologies.&quot;,&#10; &quot;main&quot;: &quot;/dist/index.js&quot;,&#10; &quot;repository&quot;: {&#10; &quot;type&quot;: &quot;git&quot;,&#10; &quot;url&quot;: &quot;https://github.com/Neikow/apimo.js.git&quot;&#10; },&#10; &quot;homepage&quot;: &quot;https://github.com/Neikow/apimo.js&quot;,&#10; &quot;bugs&quot;: {&#10; &quot;url&quot;: &quot;https://github.com/Neikow/apimo.js/issues&quot;&#10; },&#10; &quot;keywords&quot;: [&#10; &quot;api&quot;,&#10; &quot;apimo&quot;,&#10; &quot;real-estate&quot;&#10; ],&#10; &quot;scripts&quot;: {&#10; &quot;dev&quot;: &quot;&quot;,&#10; &quot;build&quot;: &quot;tsc&quot;,&#10; &quot;run&quot;: &quot;node dist/index.js&quot;,&#10; &quot;test&quot;: &quot;vitest run&quot;,&#10; &quot;test-coverage&quot;: &quot;vitest run --coverage&quot;,&#10; &quot;lint&quot;: &quot;eslint .&quot;&#10; },&#10; &quot;author&quot;: &quot;Vitaly Lysen &lt;vitaly@lysen.dev&gt; (https://lysen.dev)&quot;,&#10; &quot;license&quot;: &quot;MIT&quot;,&#10; &quot;dependencies&quot;: {&#10; &quot;bottleneck&quot;: &quot;^2.19.5&quot;,&#10; &quot;merge-anything&quot;: &quot;^6.0.6&quot;,&#10; &quot;zod&quot;: &quot;^3.21.4&quot;&#10; },&#10; &quot;devDependencies&quot;: {&#10; &quot;@antfu/eslint-config&quot;: &quot;^5.0.0&quot;,&#10; &quot;@types/node&quot;: &quot;^24.1.0&quot;,&#10; &quot;@anatine/zod-mock&quot;: &quot;^3.14.0&quot;,&#10; &quot;@faker-js/faker&quot;: &quot;^9.9.0&quot;,&#10; &quot;@vitest/coverage-v8&quot;: &quot;^3.2.4&quot;,&#10; &quot;dotenv&quot;: &quot;^17.2.1&quot;,&#10; &quot;eslint&quot;: &quot;^9.32.0&quot;,&#10; &quot;typescript&quot;: &quot;^5.9.2&quot;,&#10; &quot;vitest&quot;: &quot;^3.2.4&quot;&#10; }&#10;}" />
28
+ </PendingDiffInfo>
29
+ </value>
30
+ </entry>
31
+ <entry key="$PROJECT_DIR$/src/services/storage/dummy.cache.test.ts">
32
+ <value>
33
+ <PendingDiffInfo>
34
+ <option name="filePath" value="$PROJECT_DIR$/src/services/storage/dummy.cache.test.ts" />
35
+ <option name="originalContent" value="import type { CatalogName } from '../../consts/catalogs'&#10;import type { ApiCulture } from '../../consts/languages'&#10;import { afterEach, beforeEach, describe, expect, it } from 'vitest'&#10;import { DummyCache } from './dummy.cache'&#10;import { CacheExpiredError } from './types'&#10;&#10;describe('cache - Dummy', () =&gt; {&#10; let cache: DummyCache&#10;&#10; beforeEach(() =&gt; {&#10; cache = new DummyCache()&#10; })&#10;&#10; afterEach(() =&gt; {&#10; // No cleanup needed for dummy cache&#10; })&#10;&#10; describe('constructor', () =&gt; {&#10; it('should create an instance without any configuration', () =&gt; {&#10; const dummyCache = new DummyCache()&#10; expect(dummyCache).toBeInstanceOf(DummyCache)&#10; })&#10; })&#10;&#10; describe('setEntries', () =&gt; {&#10; const culture: ApiCulture = 'en'&#10; const entries = [&#10; { id: 1, name: 'Item 1', name_plurial: 'Items 1' },&#10; { id: 2, name: 'Item 2', name_plurial: 'Items 2' },&#10; ]&#10;&#10; it('should not throw when setting entries', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined()&#10; })&#10;&#10; it('should handle empty entries array', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.setEntries(catalogName, culture, [])).resolves.toBeUndefined()&#10; })&#10;&#10; it('should handle different catalog and culture combinations', async () =&gt; {&#10; await expect(cache.setEntries('book_step', 'en', entries)).resolves.toBeUndefined()&#10; await expect(cache.setEntries('property_land', 'fr', entries)).resolves.toBeUndefined()&#10; await expect(cache.setEntries('property_type', 'de', entries)).resolves.toBeUndefined()&#10; })&#10; })&#10;&#10; describe('getEntry', () =&gt; {&#10; const culture: ApiCulture = 'en'&#10;&#10; it('should always throw CacheExpiredError regardless of parameters', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError for any ID', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.getEntry(catalogName, culture, 999)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry(catalogName, culture, 0)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry(catalogName, culture, -1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError for different catalogs and cultures', async () =&gt; {&#10; await expect(cache.getEntry('book_step', 'en', 1)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry('property_land', 'fr', 1)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry('property_type', 'de', 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError even after setting entries', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; const entries = [{ id: 1, name: 'Item 1', name_plurial: 'Items 1' }]&#10;&#10; await cache.setEntries(catalogName, culture, entries)&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10; })&#10;})&#10;" />
36
+ <option name="updatedContent" value="import type { CatalogName } from '../../consts/catalogs'&#10;import type { ApiCulture } from '../../consts/languages'&#10;import { afterEach, beforeEach, describe, expect, it } from 'vitest'&#10;import { DummyCache } from './dummy.cache'&#10;import { CacheExpiredError } from './types'&#10;&#10;describe('cache - Dummy', () =&gt; {&#10; let cache: DummyCache&#10;&#10; beforeEach(() =&gt; {&#10; cache = new DummyCache()&#10; })&#10;&#10; afterEach(() =&gt; {&#10; // No cleanup needed for dummy cache&#10; })&#10;&#10; describe('constructor', () =&gt; {&#10; it('should create an instance without any configuration', () =&gt; {&#10; const dummyCache = new DummyCache()&#10; expect(dummyCache).toBeInstanceOf(DummyCache)&#10; })&#10; })&#10;&#10; describe('setEntries', () =&gt; {&#10; const culture: ApiCulture = 'en'&#10; const entries = [&#10; { id: 1, name: 'Item 1', name_plurial: 'Items 1' },&#10; { id: 2, name: 'Item 2', name_plurial: 'Items 2' },&#10; ]&#10;&#10; it('should not throw when setting entries', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined()&#10; })&#10;&#10; it('should handle empty entries array', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.setEntries(catalogName, culture, [])).resolves.toBeUndefined()&#10; })&#10;&#10; it('should handle different catalog and culture combinations', async () =&gt; {&#10; await expect(cache.setEntries('book_step', 'en', entries)).resolves.toBeUndefined()&#10; await expect(cache.setEntries('property_land', 'fr', entries)).resolves.toBeUndefined()&#10; await expect(cache.setEntries('property_type', 'de', entries)).resolves.toBeUndefined()&#10; })&#10; })&#10;&#10; describe('getEntry', () =&gt; {&#10; const culture: ApiCulture = 'en'&#10;&#10; it('should always throw CacheExpiredError regardless of parameters', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError for any ID', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; await expect(cache.getEntry(catalogName, culture, 999)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry(catalogName, culture, 0)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry(catalogName, culture, -1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError for different catalogs and cultures', async () =&gt; {&#10; await expect(cache.getEntry('book_step', 'en', 1)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry('property_land', 'fr', 1)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry('property_type', 'de', 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should throw CacheExpiredError even after setting entries', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; const entries = [{ id: 1, name: 'Item 1', name_plurial: 'Items 1' }]&#10;&#10; await cache.setEntries(catalogName, culture, entries)&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10; })&#10;&#10; describe('behavior consistency', () =&gt; {&#10; it('should behave consistently across multiple calls', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; const culture: ApiCulture = 'en'&#10; const entries = [{ id: 1, name: 'Item 1', name_plurial: 'Items 1' }]&#10;&#10; // Multiple setEntries calls should not throw&#10; await expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined()&#10; await expect(cache.setEntries(catalogName, culture, entries)).resolves.toBeUndefined()&#10;&#10; // Multiple getEntry calls should always throw&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10; })&#10;&#10; it('should maintain dummy behavior regardless of cache state', async () =&gt; {&#10; const catalogName: CatalogName = 'book_step'&#10; const culture: ApiCulture = 'en'&#10;&#10; // Should throw before any operations&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10;&#10; // Should still throw after setting entries&#10; await cache.setEntries(catalogName, culture, [{ id: 1, name: 'Test', name_plurial: 'Tests' }])&#10; await expect(cache.getEntry(catalogName, culture, 1)).rejects.toThrow(CacheExpiredError)&#10;&#10; // Should still throw after multiple operations&#10; await cache.setEntries(catalogName, culture, [])&#10; await expect(cache.getEntry(catalogName, culture, 999)).rejects.toThrow(CacheExpiredError)&#10; })&#10; })&#10;})" />
37
+ </PendingDiffInfo>
38
+ </value>
39
+ </entry>
40
+ </map>
41
+ </option>
42
+ </component>
43
+ </project>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ </profile>
6
+ </component>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="EslintConfiguration">
4
+ <option name="fix-on-save" value="true" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/apimo.js.iml" filepath="$PROJECT_DIR$/.idea/apimo.js.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PrettierConfiguration">
4
+ <option name="myConfigurationMode" value="AUTOMATIC" />
5
+ </component>
6
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Apimo.js
2
+
3
+ Apimo.js is a Node.js module for interacting with the Apimo API.
4
+
5
+ ## Usage
6
+
7
+ ```javascript
8
+ const Apimo = require('apimo.js/dist')
9
+
10
+ const api = new Apimo(
11
+ '<BRIDGE_ID>',
12
+ '<API_TOKEN>',
13
+ {
14
+ debug: true,
15
+ cultures: [
16
+ 'fr_FR',
17
+ 'en_GB',
18
+ ],
19
+ updateIntervals: {
20
+ properties: 1000 * 60 * 5, // 5 minutes
21
+ catalogs: 1000 * 60 * 60 * 24, // 24 hours
22
+ }
23
+ }
24
+ )
25
+ ```
26
+
27
+ The `BRIDGE_ID` and `API_TOKEN` can be asked on the support page of Apimo.
28
+
29
+ ## API
30
+
31
+ The API endpoints are described on the [Apimo API documentation](https://apimo.net/en/api/webservice/).
32
+
33
+ ## Wrapper
34
+
35
+ The wrapper is a set of methods that will help you to interact with the API.
36
+ It can cache requests in order to avoid reaching the API rate limit, which are pretty low (1000 requests per day).
37
+ The data can be set to be updated automatically, given a certain interval.
38
+
39
+ ## Methods
40
+
41
+ ### `get`
42
+
43
+ Helper method to get a property from the API.
44
+
45
+ ```javascript
46
+ api.get(['properties', 123]).then((property) => {
47
+ console.log(property) // == Property(id: 123, ...)
48
+ })
49
+ ```
50
+
51
+ ### `getProperties`
52
+
53
+ Get all properties from the API.
54
+
55
+ ```javascript
56
+ api.fetchProperties().then((properties) => {
57
+ console.log(properties) // [Property(...), Property(...), ...]
58
+ })
59
+ ```
60
+
61
+ A `Property` is the data of a property, as stored in the Apimo API, it is described in
62
+ the [Apimo API documentation](https://apimo.net/en/api/webservice/?child=agencies/properties#get-agencies/propertiesagencies-{agency_id}-properties).
63
+
64
+ ### `getAgencies`
65
+
66
+ Returns the agencies linked to the bridge.
67
+
68
+ ```javascript
69
+ api.fetchAgencies().then((agencies) => {
70
+ console.log(agencies) // [Agency(...), Agency(...), ...]
71
+ })
72
+ ```
73
+
74
+ An `Agency` is the data of an agency, as stored in the Apimo API, it is described in
75
+ the [Apimo API documentation](https://apimo.net/en/api/webservice/?child=agencies#get-agenciesagencies).
76
+
77
+ ### `convertDate`
78
+
79
+ Converts a Apimo date to a `Date` object.
80
+
81
+ ```javascript
82
+ api.convertDate('2018-01-01').then((date) => {
83
+ console.log(date) // == Date(2018, 0, 1)
84
+ })
85
+ ```
86
+
87
+ ## Installation
88
+
89
+ ```bash
90
+ npm install github:Neikow/apimo.js
91
+ ```
@@ -0,0 +1,2 @@
1
+ export declare const API_CATALOGS: readonly ["action_type", "action_method", "book_step", "book_type", "construction_step", "contact_title", "document_type", "fees", "lead_type", "lead_step", "property_activity", "property_areas", "property_adjacency", "property_agreement", "property_availability", "property_building", "property_category", "property_subcategory", "property_condition", "property_construction_method", "property_floor", "property_flooring", "property_heating_device", "property_heating_access", "property_heating_type", "property_hot_water_device", "property_hot_water_access", "property_land", "property_lease", "property_location", "property_orientation", "property_period", "property_proximity", "property_reglementation", "property_regulation", "property_financial", "property_service", "property_service_category", "property_standing", "property_step", "property_status", "property_type", "property_subtype", "property_view_landscape", "property_view_type", "property_waste_water", "referral", "tags", "unit_area", "unit_length", "user_group"];
2
+ export type CatalogName = typeof API_CATALOGS[number];
@@ -0,0 +1,53 @@
1
+ export const API_CATALOGS = [
2
+ 'action_type',
3
+ 'action_method',
4
+ 'book_step',
5
+ 'book_type',
6
+ 'construction_step',
7
+ 'contact_title',
8
+ 'document_type',
9
+ 'fees',
10
+ 'lead_type',
11
+ 'lead_step',
12
+ 'property_activity',
13
+ 'property_areas',
14
+ 'property_adjacency',
15
+ 'property_agreement',
16
+ 'property_availability',
17
+ 'property_building',
18
+ 'property_category',
19
+ 'property_subcategory',
20
+ 'property_condition',
21
+ 'property_construction_method',
22
+ 'property_floor',
23
+ 'property_flooring',
24
+ 'property_heating_device',
25
+ 'property_heating_access',
26
+ 'property_heating_type',
27
+ 'property_hot_water_device',
28
+ 'property_hot_water_access',
29
+ 'property_land',
30
+ 'property_lease',
31
+ 'property_location',
32
+ 'property_orientation',
33
+ 'property_period',
34
+ 'property_proximity',
35
+ 'property_reglementation',
36
+ 'property_regulation',
37
+ 'property_financial',
38
+ 'property_service',
39
+ 'property_service_category',
40
+ 'property_standing',
41
+ 'property_step',
42
+ 'property_status',
43
+ 'property_type',
44
+ 'property_subtype',
45
+ 'property_view_landscape',
46
+ 'property_view_type',
47
+ 'property_waste_water',
48
+ 'referral',
49
+ 'tags',
50
+ 'unit_area',
51
+ 'unit_length',
52
+ 'user_group',
53
+ ];
@@ -0,0 +1,2 @@
1
+ export declare const API_LANGUAGES: readonly ["fr", "it", "de", "es", "en", "nl", "zh", "ru", "sv", "ar", "he", "nb", "pt", "fa", "lb", "km", "tr", "lo"];
2
+ export type ApiCulture = (typeof API_LANGUAGES)[number];
@@ -0,0 +1,20 @@
1
+ export const API_LANGUAGES = [
2
+ 'fr',
3
+ 'it',
4
+ 'de',
5
+ 'es',
6
+ 'en',
7
+ 'nl',
8
+ 'zh',
9
+ 'ru',
10
+ 'sv',
11
+ 'ar',
12
+ 'he',
13
+ 'nb',
14
+ 'pt',
15
+ 'fa',
16
+ 'lb',
17
+ 'km',
18
+ 'tr',
19
+ 'lo',
20
+ ];