@ikatec/digisac-api-sdk 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/apis/contactBlockLists/types.d.ts +2 -1
  2. package/dist/apis/contactBlockLists/types.d.ts.map +1 -1
  3. package/dist/apis/contacts/ContactsApi.cjs +8 -0
  4. package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
  5. package/dist/apis/contacts/ContactsApi.d.ts +7 -1
  6. package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
  7. package/dist/apis/contacts/ContactsApi.mjs +8 -0
  8. package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
  9. package/dist/apis/contacts/types.d.ts +16 -0
  10. package/dist/apis/contacts/types.d.ts.map +1 -1
  11. package/dist/apis/departments/DepartmentsApi.cjs +2 -2
  12. package/dist/apis/departments/DepartmentsApi.cjs.map +1 -1
  13. package/dist/apis/departments/DepartmentsApi.d.ts +2 -2
  14. package/dist/apis/departments/DepartmentsApi.d.ts.map +1 -1
  15. package/dist/apis/departments/DepartmentsApi.mjs +2 -2
  16. package/dist/apis/departments/DepartmentsApi.mjs.map +1 -1
  17. package/dist/apis/departments/types.d.ts +1 -0
  18. package/dist/apis/departments/types.d.ts.map +1 -1
  19. package/dist/apis/kanbanBoards/types.d.ts +2 -0
  20. package/dist/apis/kanbanBoards/types.d.ts.map +1 -1
  21. package/dist/apis/services/ServicesApi.cjs +2 -2
  22. package/dist/apis/services/ServicesApi.cjs.map +1 -1
  23. package/dist/apis/services/ServicesApi.d.ts +2 -2
  24. package/dist/apis/services/ServicesApi.d.ts.map +1 -1
  25. package/dist/apis/services/ServicesApi.mjs +2 -2
  26. package/dist/apis/services/ServicesApi.mjs.map +1 -1
  27. package/dist/apis/services/types.d.ts +1 -0
  28. package/dist/apis/services/types.d.ts.map +1 -1
  29. package/dist/apis/tags/types.d.ts +2 -0
  30. package/dist/apis/tags/types.d.ts.map +1 -1
  31. package/dist/apis/ticketTopics/TicketTopicsApi.cjs +17 -0
  32. package/dist/apis/ticketTopics/TicketTopicsApi.cjs.map +1 -1
  33. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts +11 -0
  34. package/dist/apis/ticketTopics/TicketTopicsApi.d.ts.map +1 -1
  35. package/dist/apis/ticketTopics/TicketTopicsApi.mjs +17 -0
  36. package/dist/apis/ticketTopics/TicketTopicsApi.mjs.map +1 -1
  37. package/dist/apis/tickets/TicketsApi.cjs +11 -0
  38. package/dist/apis/tickets/TicketsApi.cjs.map +1 -1
  39. package/dist/apis/tickets/TicketsApi.d.ts +10 -1
  40. package/dist/apis/tickets/TicketsApi.d.ts.map +1 -1
  41. package/dist/apis/tickets/TicketsApi.mjs +11 -0
  42. package/dist/apis/tickets/TicketsApi.mjs.map +1 -1
  43. package/dist/apis/tickets/types.d.ts +18 -0
  44. package/dist/apis/tickets/types.d.ts.map +1 -1
  45. package/dist/apis/users/UsersApi.cjs +2 -2
  46. package/dist/apis/users/UsersApi.cjs.map +1 -1
  47. package/dist/apis/users/UsersApi.d.ts +2 -2
  48. package/dist/apis/users/UsersApi.d.ts.map +1 -1
  49. package/dist/apis/users/UsersApi.mjs +2 -2
  50. package/dist/apis/users/UsersApi.mjs.map +1 -1
  51. package/dist/apis/users/types.d.ts +1 -0
  52. package/dist/apis/users/types.d.ts.map +1 -1
  53. package/dist/core/ArchivableCrudApi.cjs +39 -0
  54. package/dist/core/ArchivableCrudApi.cjs.map +1 -0
  55. package/dist/core/ArchivableCrudApi.d.ts +31 -0
  56. package/dist/core/ArchivableCrudApi.d.ts.map +1 -0
  57. package/dist/core/ArchivableCrudApi.mjs +38 -0
  58. package/dist/core/ArchivableCrudApi.mjs.map +1 -0
  59. package/dist/core/BaseCrudApi.cjs +13 -0
  60. package/dist/core/BaseCrudApi.cjs.map +1 -1
  61. package/dist/core/BaseCrudApi.d.ts +6 -0
  62. package/dist/core/BaseCrudApi.d.ts.map +1 -1
  63. package/dist/core/BaseCrudApi.mjs +13 -0
  64. package/dist/core/BaseCrudApi.mjs.map +1 -1
  65. package/dist/core/index.cjs +2 -0
  66. package/dist/core/index.d.ts +1 -0
  67. package/dist/core/index.d.ts.map +1 -1
  68. package/dist/core/index.mjs +2 -1
  69. package/dist/index.cjs +2 -0
  70. package/dist/index.mjs +2 -1
  71. package/package.json +1 -1
  72. package/src/apis/contactBlockLists/types.ts +2 -1
  73. package/src/apis/contacts/ContactsApi.test.ts +24 -0
  74. package/src/apis/contacts/ContactsApi.ts +18 -0
  75. package/src/apis/contacts/types.ts +17 -0
  76. package/src/apis/departments/DepartmentsApi.ts +2 -2
  77. package/src/apis/departments/types.ts +1 -0
  78. package/src/apis/kanbanBoards/types.ts +2 -0
  79. package/src/apis/services/ServicesApi.ts +6 -2
  80. package/src/apis/services/types.ts +1 -0
  81. package/src/apis/tags/types.ts +2 -0
  82. package/src/apis/ticketTopics/TicketTopicsApi.test.ts +58 -0
  83. package/src/apis/ticketTopics/TicketTopicsApi.ts +27 -0
  84. package/src/apis/tickets/TicketsApi.test.ts +64 -0
  85. package/src/apis/tickets/TicketsApi.ts +22 -1
  86. package/src/apis/tickets/types.ts +12 -0
  87. package/src/apis/users/UsersApi.ts +2 -2
  88. package/src/apis/users/types.ts +1 -0
  89. package/src/core/ArchivableCrudApi.test.ts +84 -0
  90. package/src/core/ArchivableCrudApi.ts +42 -0
  91. package/src/core/BaseCrudApi.test.ts +31 -0
  92. package/src/core/BaseCrudApi.ts +14 -0
  93. package/src/core/index.ts +1 -0
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { TicketTopicsApi } from './TicketTopicsApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
4
+ import type { TicketTopic } from './types'
5
+
6
+ function createMockClient(): ApiClient {
7
+ return {
8
+ setAccessToken: vi.fn().mockReturnThis(),
9
+ request: vi.fn(),
10
+ get: vi.fn(),
11
+ post: vi.fn(),
12
+ put: vi.fn(),
13
+ patch: vi.fn(),
14
+ delete: vi.fn(),
15
+ }
16
+ }
17
+
18
+ describe('TicketTopicsApi', () => {
19
+ let client: ReturnType<typeof createMockClient>
20
+ let api: TicketTopicsApi
21
+
22
+ beforeEach(() => {
23
+ client = createMockClient()
24
+ api = new TicketTopicsApi(client)
25
+ })
26
+
27
+ it('archives via POST /ticket-topics/:id/archive with { archive: true }', async () => {
28
+ const topic = { id: 't1' } as TicketTopic
29
+ vi.mocked(client.post).mockResolvedValue(topic)
30
+
31
+ const result = await api.archive('t1')
32
+ expect(client.post).toHaveBeenCalledWith(
33
+ '/ticket-topics/t1/archive',
34
+ { archive: true },
35
+ undefined,
36
+ )
37
+ expect(result).toEqual(topic)
38
+ })
39
+
40
+ it('unarchives via POST /ticket-topics/:id/archive with { archive: false }', async () => {
41
+ vi.mocked(client.post).mockResolvedValue({ id: 't1' } as TicketTopic)
42
+
43
+ await api.unarchive('t1')
44
+ expect(client.post).toHaveBeenCalledWith(
45
+ '/ticket-topics/t1/archive',
46
+ { archive: false },
47
+ undefined,
48
+ )
49
+ })
50
+
51
+ it('archive/unarchive stay bound when destructured', async () => {
52
+ vi.mocked(client.post).mockResolvedValue({ id: 't1' } as TicketTopic)
53
+ const { archive, unarchive } = api
54
+ await archive('t1')
55
+ await unarchive('t1')
56
+ expect(client.post).toHaveBeenCalledTimes(2)
57
+ })
58
+ })
@@ -9,5 +9,32 @@ export class TicketTopicsApi extends BaseCrudApi<
9
9
  > {
10
10
  constructor(client: ApiClient) {
11
11
  super(client, '/ticket-topics')
12
+ this.archive = this.archive.bind(this)
13
+ this.unarchive = this.unarchive.bind(this)
14
+ }
15
+
16
+ /**
17
+ * Archives a ticket topic. Unlike most resources, ticket-topics archive through a
18
+ * dedicated endpoint (POST `${basePath}/${id}/archive`) rather than `updateById`.
19
+ * @permissions ticketTopic.update
20
+ */
21
+ archive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
22
+ return this.client.post<TicketTopic>(
23
+ `${this.basePath}/${id}/archive`,
24
+ { archive: true },
25
+ headers,
26
+ )
27
+ }
28
+
29
+ /**
30
+ * Restores a previously archived ticket topic via the same dedicated endpoint.
31
+ * @permissions ticketTopic.update
32
+ */
33
+ unarchive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
34
+ return this.client.post<TicketTopic>(
35
+ `${this.basePath}/${id}/archive`,
36
+ { archive: false },
37
+ headers,
38
+ )
12
39
  }
13
40
  }
@@ -0,0 +1,64 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { TicketsApi } from './TicketsApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ // ── Tests ────────────────────────────────────────────────────────────
20
+
21
+ describe('TicketsApi', () => {
22
+ let client: ReturnType<typeof createMockClient>
23
+ let api: TicketsApi
24
+
25
+ beforeEach(() => {
26
+ client = createMockClient()
27
+ api = new TicketsApi(client)
28
+ })
29
+
30
+ describe('closeMany', () => {
31
+ it('PUTs /tickets/close-many with explicit ids', async () => {
32
+ vi.mocked(client.put).mockResolvedValue({ ok: true })
33
+ const result = await api.closeMany({ ids: ['t1', 't2'] })
34
+ expect(client.put).toHaveBeenCalledWith(
35
+ '/tickets/close-many',
36
+ { ids: ['t1', 't2'] },
37
+ undefined,
38
+ )
39
+ expect(result).toEqual({ ok: true })
40
+ })
41
+
42
+ it('PUTs /tickets/close-many with a JSON-stringified query for the select-all variant', async () => {
43
+ vi.mocked(client.put).mockResolvedValue({ ok: true })
44
+ await api.closeMany({ allContactsSelected: true, query: { query: '{"where":{}}' } })
45
+ expect(client.put).toHaveBeenCalledWith(
46
+ '/tickets/close-many',
47
+ { allContactsSelected: true, query: { query: '{"where":{}}' } },
48
+ undefined,
49
+ )
50
+ })
51
+
52
+ it('forwards custom headers', async () => {
53
+ vi.mocked(client.put).mockResolvedValue({ ok: true })
54
+ const headers = { 'X-Custom': 'value' }
55
+ await api.closeMany({ ids: ['t1'] }, headers)
56
+ expect(client.put).toHaveBeenCalledWith('/tickets/close-many', { ids: ['t1'] }, headers)
57
+ })
58
+
59
+ it('propagates errors', async () => {
60
+ vi.mocked(client.put).mockRejectedValue(new Error('Forbidden'))
61
+ await expect(api.closeMany({ ids: ['t1'] })).rejects.toThrow('Forbidden')
62
+ })
63
+ })
64
+ })
@@ -1,9 +1,30 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
- import type { Ticket, CreateTicketPayload, UpdateTicketPayload } from './types'
3
+ import type {
4
+ Ticket,
5
+ CreateTicketPayload,
6
+ UpdateTicketPayload,
7
+ CloseManyTicketsPayload,
8
+ } from './types'
4
9
 
5
10
  export class TicketsApi extends BaseCrudApi<Ticket, CreateTicketPayload, UpdateTicketPayload> {
6
11
  constructor(client: ApiClient) {
7
12
  super(client, '/tickets')
8
13
  }
14
+
15
+ /**
16
+ * Closes multiple tickets in a single request. This is the only ticket-close
17
+ * endpoint exposed by the API — there is no per-ticket close/update route.
18
+ *
19
+ * Pass `{ ids }` to close specific tickets, or `{ allContactsSelected: true, query }`
20
+ * to close every OPEN ticket matching `query` (the backend forces `isOpen: true`).
21
+ * @permissions tickets.close.all OR tickets.close.me
22
+ */
23
+ closeMany(payload: CloseManyTicketsPayload, headers?: Record<string, string>): Promise<unknown> {
24
+ return this.client.put<unknown, CloseManyTicketsPayload>(
25
+ `${this.basePath}/close-many`,
26
+ payload,
27
+ headers,
28
+ )
29
+ }
9
30
  }
@@ -67,3 +67,15 @@ export type UpdateTicketPayload = {
67
67
  comments?: string | null
68
68
  isOpen?: boolean
69
69
  }
70
+
71
+ /**
72
+ * Body for `PUT /tickets/close-many`.
73
+ *
74
+ * Either close an explicit list of tickets by id, or close every open ticket
75
+ * matching a query. For the query variant the outer `query` is an object whose
76
+ * `query` field is a JSON-stringified list query (e.g. `JSON.stringify({ where: {} })`);
77
+ * the backend always forces `where.isOpen = true`.
78
+ */
79
+ export type CloseManyTicketsPayload =
80
+ | { ids: string[]; ticketTopicIds?: string[] | string; comments?: string }
81
+ | { allContactsSelected: true; query: { query: string } }
@@ -1,8 +1,8 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
- import { BaseCrudApi } from '../../core/BaseCrudApi'
2
+ import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
3
3
  import type { User, CreateUserPayload, UpdateUserPayload } from './types'
4
4
 
5
- export class UsersApi extends BaseCrudApi<User, CreateUserPayload, UpdateUserPayload> {
5
+ export class UsersApi extends ArchivableCrudApi<User, CreateUserPayload, UpdateUserPayload> {
6
6
  constructor(client: ApiClient) {
7
7
  super(client, '/users')
8
8
  }
@@ -67,4 +67,5 @@ export type UpdateUserPayload = {
67
67
  timetableId?: string | null
68
68
  preferences?: UserPreferences
69
69
  language?: string
70
+ archivedAt?: string | null
70
71
  }
@@ -0,0 +1,84 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { ArchivableCrudApi } from './ArchivableCrudApi'
3
+ import type { ApiClient } from './ApiClient'
4
+
5
+ // ── Test types ───────────────────────────────────────────────────────
6
+
7
+ type Item = { id: string; name: string; archivedAt: string | null }
8
+ type CreateItem = { name: string }
9
+ type UpdateItem = { name?: string; archivedAt?: string | null }
10
+
11
+ // ── Mock client factory ──────────────────────────────────────────────
12
+
13
+ function createMockClient(): ApiClient {
14
+ return {
15
+ setAccessToken: vi.fn().mockReturnThis(),
16
+ request: vi.fn(),
17
+ get: vi.fn(),
18
+ post: vi.fn(),
19
+ put: vi.fn(),
20
+ patch: vi.fn(),
21
+ delete: vi.fn(),
22
+ }
23
+ }
24
+
25
+ // ── Tests ────────────────────────────────────────────────────────────
26
+
27
+ describe('ArchivableCrudApi', () => {
28
+ let client: ReturnType<typeof createMockClient>
29
+ let api: ArchivableCrudApi<Item, CreateItem, UpdateItem>
30
+
31
+ beforeEach(() => {
32
+ client = createMockClient()
33
+ api = new ArchivableCrudApi<Item, CreateItem, UpdateItem>(client, '/items')
34
+ })
35
+
36
+ it('inherits BaseCrudApi behavior (deleteById)', async () => {
37
+ vi.mocked(client.delete).mockResolvedValue({ id: '1', name: 'x', archivedAt: null })
38
+ await api.deleteById('1')
39
+ expect(client.delete).toHaveBeenCalledWith('/items/1', undefined)
40
+ })
41
+
42
+ describe('archive', () => {
43
+ it('PUTs basePath/id with an archivedAt timestamp', async () => {
44
+ const archived: Item = { id: 'abc', name: 'x', archivedAt: '2026-01-01T00:00:00.000Z' }
45
+ vi.mocked(client.put).mockResolvedValue(archived)
46
+
47
+ const result = await api.archive('abc')
48
+
49
+ const [path, body, headers] = vi.mocked(client.put).mock.calls.at(0) ?? []
50
+ expect(path).toBe('/items/abc')
51
+ expect(typeof (body as { archivedAt?: unknown }).archivedAt).toBe('string')
52
+ expect(headers).toBeUndefined()
53
+ expect(result).toEqual(archived)
54
+ })
55
+
56
+ it('forwards custom headers', async () => {
57
+ vi.mocked(client.put).mockResolvedValue({ id: '1', name: 'x', archivedAt: null })
58
+ const headers = { 'X-Reason': 'cleanup' }
59
+
60
+ await api.archive('1', headers)
61
+ expect(vi.mocked(client.put).mock.calls.at(0)?.at(2)).toEqual(headers)
62
+ })
63
+ })
64
+
65
+ describe('unarchive', () => {
66
+ it('PUTs basePath/id with archivedAt null', async () => {
67
+ const restored: Item = { id: 'abc', name: 'x', archivedAt: null }
68
+ vi.mocked(client.put).mockResolvedValue(restored)
69
+
70
+ const result = await api.unarchive('abc')
71
+
72
+ expect(client.put).toHaveBeenCalledWith('/items/abc', { archivedAt: null }, undefined)
73
+ expect(result).toEqual(restored)
74
+ })
75
+ })
76
+
77
+ it('archive/unarchive stay bound when destructured', async () => {
78
+ vi.mocked(client.put).mockResolvedValue({ id: '1', name: 'x', archivedAt: null })
79
+ const { archive, unarchive } = api
80
+ await archive('1')
81
+ await unarchive('1')
82
+ expect(client.put).toHaveBeenCalledTimes(2)
83
+ })
84
+ })
@@ -0,0 +1,42 @@
1
+ import type { ApiClient } from './ApiClient'
2
+ import { BaseCrudApi } from './BaseCrudApi'
3
+ import type { ListQuery } from './types'
4
+
5
+ /**
6
+ * CRUD base for resources that support soft-delete via an `archivedAt` timestamp
7
+ * (PUT `${basePath}/${id}` with `{ archivedAt }`). Adds `archive`/`unarchive` on
8
+ * top of {@link BaseCrudApi}.
9
+ *
10
+ * Only extend this for resources whose archive write mechanism is `archivedAt`.
11
+ * Resources that archive through a different field (e.g. cards use `isArchived`)
12
+ * or a dedicated endpoint (e.g. ticket-topics POST `/archive`) must NOT use this
13
+ * base and should implement `archive`/`unarchive` themselves.
14
+ */
15
+ export class ArchivableCrudApi<
16
+ TResponse,
17
+ TCreate,
18
+ TUpdate extends { archivedAt?: string | null } = TCreate & { archivedAt?: string | null },
19
+ TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
20
+ > extends BaseCrudApi<TResponse, TCreate, TUpdate, TQuery> {
21
+ constructor(client: ApiClient, basePath: string) {
22
+ super(client, basePath)
23
+ this.archive = this.archive.bind(this)
24
+ this.unarchive = this.unarchive.bind(this)
25
+ }
26
+
27
+ /**
28
+ * Archives a resource (soft-delete) by setting `archivedAt` to the current time.
29
+ * @permissions $resourceName.update
30
+ */
31
+ archive(id: string, headers?: Record<string, string>): Promise<TResponse> {
32
+ return this.updateById(id, { archivedAt: new Date().toISOString() } as TUpdate, headers)
33
+ }
34
+
35
+ /**
36
+ * Restores a previously archived resource by clearing `archivedAt`.
37
+ * @permissions $resourceName.update
38
+ */
39
+ unarchive(id: string, headers?: Record<string, string>): Promise<TResponse> {
40
+ return this.updateById(id, { archivedAt: null } as TUpdate, headers)
41
+ }
42
+ }
@@ -131,6 +131,37 @@ describe('BaseCrudApi', () => {
131
131
  })
132
132
  })
133
133
 
134
+ // ── getOne ───────────────────────────────────────────────────────
135
+
136
+ describe('getOne', () => {
137
+ it('requests limit 1 (non-paginated) and returns the first item', async () => {
138
+ const item: Item = { id: '1', name: 'Test' }
139
+ vi.mocked(client.get).mockResolvedValue([item])
140
+
141
+ const result = await api.getOne()
142
+ const calledUrl = vi.mocked(client.get).mock.calls.at(0)?.at(0) as string
143
+ expect(calledUrl).toContain('/items?')
144
+ expect(calledUrl).toContain('limit=1')
145
+ expect(calledUrl).toContain('paginate=false')
146
+ expect(result).toEqual(item)
147
+ })
148
+
149
+ it('returns null when no records match', async () => {
150
+ vi.mocked(client.get).mockResolvedValue([])
151
+
152
+ const result = await api.getOne({ where: { name: 'missing' } })
153
+ expect(result).toBeNull()
154
+ })
155
+
156
+ it('forwards custom headers', async () => {
157
+ vi.mocked(client.get).mockResolvedValue([])
158
+ const headers = { 'X-Custom': 'value' }
159
+
160
+ await api.getOne(undefined, headers)
161
+ expect(vi.mocked(client.get).mock.calls.at(0)?.at(1)).toEqual(headers)
162
+ })
163
+ })
164
+
134
165
  // ── getById ──────────────────────────────────────────────────────
135
166
 
136
167
  describe('getById', () => {
@@ -28,6 +28,7 @@ export class BaseCrudApi<
28
28
  this.client = client
29
29
  this.basePath = basePath.replace(/\/$/, '')
30
30
  this.getMany = this.getMany.bind(this)
31
+ this.getOne = this.getOne.bind(this)
31
32
  this.getById = this.getById.bind(this)
32
33
  this.create = this.create.bind(this)
33
34
  this.updateById = this.updateById.bind(this)
@@ -53,6 +54,19 @@ export class BaseCrudApi<
53
54
  )
54
55
  }
55
56
 
57
+ /**
58
+ * Returns the first resource matching the query, or `null` when none match.
59
+ * Convenience over `getMany` with `limit: 1`.
60
+ * @permissions $resourceName.view
61
+ */
62
+ async getOne(query?: TQuery, headers?: Record<string, string>): Promise<TResponse | null> {
63
+ const results = await this.getMany(
64
+ { ...query, limit: 1, paginate: false } as TQuery & { paginate: false },
65
+ headers,
66
+ )
67
+ return results[0] ?? null
68
+ }
69
+
56
70
  /**
57
71
  * Returns a single resource by ID.
58
72
  * @permissions $resourceName.view
package/src/core/index.ts CHANGED
@@ -2,4 +2,5 @@ export type { ApiClient, HttpMethod } from './ApiClient'
2
2
  export { BaseApiClient, ApiError } from './BaseApiClient'
3
3
  export type { ValidationError } from './BaseApiClient'
4
4
  export { BaseCrudApi } from './BaseCrudApi'
5
+ export { ArchivableCrudApi } from './ArchivableCrudApi'
5
6
  export type { WhereClause, IncludeItem, ListQuery, GetByIdQuery, Paginated } from './types'