@things-factory/inquiry 10.1.253 → 10.1.255

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.
@@ -1,218 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '@operato/data-grist/ox-grist.js'
3
- import '@operato/data-grist/ox-filters-form.js'
4
- import '@operato/input/ox-select.js'
5
- import '@operato/popup/ox-popup-list.js'
6
- import '../components/inquiry-thread.js'
7
-
8
- import gql from 'graphql-tag'
9
- import { css, html, nothing } from 'lit'
10
- import { customElement, query, state } from 'lit/decorators.js'
11
-
12
- import { DataGrist, FetchOption } from '@operato/data-grist'
13
- import { client } from '@operato/graphql'
14
- import { i18next, localize } from '@operato/i18n'
15
- import { navigate, PageView } from '@operato/shell'
16
- import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'
17
- import { isMobileDevice } from '@operato/utils'
18
-
19
- import { openInquiryWriter } from '../components/inquiry-writer.js'
20
- import type { InquiryThread } from '../components/inquiry-thread.js'
21
- import { inquiryApi, LIST_FIELDS } from '../utils/inquiry-api.js'
22
- import { KIND_ICONS, KINDS, kindWord, STATUSES, statusWord, t, visibilityWord, when } from '../utils/inquiry-words.js'
23
-
24
- /**
25
- * 문의 · 제안 — the questions, bug reports and proposals of the domain that the person may read, and with `?id=` one of
26
- * them as its thread (ADR-0184 decision 8). Handlers work from here too: they open on those waiting on them.
27
- */
28
- @customElement('inquiry-list')
29
- export class InquiryList extends localize(i18next)(PageView) {
30
- static styles = [
31
- WorkSurfacePageStyles,
32
- CommonGristStyles,
33
- CommonHeaderStyles,
34
- ScrollbarStyles,
35
- css`
36
- .filters {
37
- display: flex;
38
- flex-wrap: wrap;
39
- align-items: center;
40
- gap: var(--spacing-medium, 8px);
41
- }
42
- ox-select {
43
- min-width: 140px;
44
- }
45
- .thread {
46
- overflow: auto;
47
- padding: var(--spacing-large, 12px) var(--spacing-huge, 16px);
48
- max-width: 880px;
49
- width: 100%;
50
- box-sizing: border-box;
51
- }
52
- `
53
- ]
54
-
55
- @state() gristConfig: any
56
- @state() scope = 'all'
57
- @state() handler = false
58
- /* The inquiry shown as its thread - none shows the list. */
59
- @state() openId = ''
60
- @query('ox-grist') private grist!: DataGrist
61
- @query('inquiry-thread') private thread!: InquiryThread
62
-
63
- private onChanged = () => {
64
- if (this.openId) return
65
- this.grist?.fetch()
66
- }
67
-
68
- get context() {
69
- return {
70
- title: t('inquiry.title.list'),
71
- ...(this.openId
72
- ? {}
73
- : {
74
- search: {
75
- handler: (search: string) => {
76
- this.grist.searchText = search
77
- },
78
- value: this.grist?.searchText || ''
79
- },
80
- filter: {
81
- handler: () => {
82
- this.grist.toggleHeadroom()
83
- }
84
- }
85
- }),
86
- actions: [
87
- ...(this.openId ? [{ title: t('inquiry.button.back'), icon: 'arrow_back', action: () => navigate('inquiry-list') }] : []),
88
- { title: t('inquiry.button.send'), icon: 'add', emphasis: { raised: true }, action: () => openInquiryWriter() }
89
- ]
90
- }
91
- }
92
-
93
- render() {
94
- if (this.openId) return html`<div class="thread"><inquiry-thread .inquiryId=${this.openId}></inquiry-thread></div>`
95
- const scopes = [
96
- { value: 'all', display: t('inquiry.scope.all') },
97
- { value: 'mine', display: t('inquiry.scope.mine') },
98
- ...(this.handler ? [{ value: 'handlers-turn', display: t('inquiry.scope.handlers-turn') }] : [])
99
- ]
100
- return html`<ox-grist class="page-fill" search-mode="term" .mode=${isMobileDevice() ? 'CARD' : 'GRID'} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
101
- <div slot="headroom" class="header">
102
- <div class="filters">
103
- <ox-select
104
- .value=${this.scope}
105
- aria-label=${t('inquiry.text.scope')}
106
- @change=${(e: CustomEvent) => {
107
- const next = String(e.detail ?? 'all')
108
- if (next === this.scope) return
109
- this.scope = next
110
- this.grist.fetch()
111
- }}
112
- >
113
- <span slot="label">${scopes.find(one => one.value === this.scope)?.display}</span>
114
- <ox-popup-list align-left nowrap>${scopes.map(one => html`<div option value=${one.value}>${one.display}</div>`)}</ox-popup-list>
115
- </ox-select>
116
- <ox-filters-form without-search autofocus></ox-filters-form>
117
- </div>
118
- </div>
119
- </ox-grist>`
120
- }
121
-
122
- connectedCallback() {
123
- super.connectedCallback()
124
- window.addEventListener('inquiries-changed', this.onChanged)
125
- }
126
-
127
- disconnectedCallback() {
128
- window.removeEventListener('inquiries-changed', this.onChanged)
129
- super.disconnectedCallback()
130
- }
131
-
132
- async pageUpdated(changes: any, lifecycle: any) {
133
- if (!this.active || !(changes.active || changes.params)) return
134
- const id = String(lifecycle?.params?.id ?? '')
135
- if (id !== this.openId) {
136
- this.openId = id
137
- this.updateContext()
138
- } else if (id) this.thread?.load(true)
139
- else if (this.gristConfig) this.grist?.fetch()
140
- }
141
-
142
- async pageInitialized() {
143
- try {
144
- this.handler = (await inquiryApi.summary()).handler
145
- /* A handler opens on what waits on them. */
146
- if (this.handler) this.scope = 'handlers-turn'
147
- } catch {
148
- this.handler = false
149
- }
150
- const open = (record: any) => record?.id && navigate(`inquiry-list?id=${record.id}`)
151
- this.gristConfig = {
152
- rows: { appendable: false, selectable: false, handlers: { click: (_c: any, _d: any, _col: any, record: any) => open(record) } },
153
- pagination: { limit: 50, pages: [50, 100] },
154
- emptyNote: { title: t('inquiry.text.empty'), description: t('inquiry.text.empty-description') },
155
- list: { fields: ['title', 'status'], details: ['kind', 'createdAt'] },
156
- columns: [
157
- {
158
- type: 'string',
159
- name: 'kind',
160
- header: t('inquiry.field.kind'),
161
- width: 110,
162
- record: {
163
- editable: false,
164
- renderer: (value: string) => html`<span style="display:inline-flex;align-items:center;gap:2px"><md-icon style="--md-icon-size:16px">${KIND_ICONS[value] ?? 'help'}</md-icon>${kindWord(value)}</span>`
165
- },
166
- filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...KINDS.map(code => ({ display: kindWord(code), value: code }))] }
167
- },
168
- {
169
- type: 'string',
170
- name: 'title',
171
- header: t('inquiry.field.title'),
172
- width: 320,
173
- record: {
174
- editable: false,
175
- renderer: (value: string, _c: any, record: any) =>
176
- html`<span style=${record?.isNew ? 'font-weight:600' : ''}
177
- >${record?.visibility === 'PRIVATE' ? html`<md-icon style="--md-icon-size:14px" title=${visibilityWord('PRIVATE')}>lock</md-icon> ` : nothing}${value}${record?.replyCount
178
- ? ` (${record.replyCount})`
179
- : ''}</span
180
- >`
181
- }
182
- },
183
- {
184
- type: 'string',
185
- name: 'status',
186
- header: t('inquiry.field.status'),
187
- width: 100,
188
- record: {
189
- editable: false,
190
- renderer: (value: string, _c: any, record: any) =>
191
- html`<span style=${record?.isNew ? 'color:var(--md-sys-color-primary);font-weight:600' : ''}>${statusWord(value)}${record?.isNew ? ` · ${t('inquiry.text.new')}` : ''}</span>`
192
- },
193
- filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...STATUSES.map(code => ({ display: statusWord(code), value: code }))] }
194
- },
195
- { type: 'string', name: 'creator', header: t('inquiry.field.writer'), width: 110, record: { editable: false, renderer: (value: any) => value?.name ?? '' } },
196
- { type: 'string', name: 'assignee', header: t('inquiry.field.assignee'), width: 110, record: { editable: false, renderer: (value: any) => value?.name ?? '' } },
197
- { type: 'string', name: 'createdAt', header: t('inquiry.field.created-at'), width: 130, record: { editable: false, renderer: (value: string) => when(value) } }
198
- ]
199
- }
200
- }
201
-
202
- async fetchHandler({ page = 1, limit = 50, filters = [], search }: FetchOption) {
203
- const response = await client.query({
204
- query: gql`
205
- query ($filters: [Filter!], $pagination: Pagination, $search: String, $scope: String) {
206
- responses: inquiries(filters: $filters, pagination: $pagination, search: $search, scope: $scope) {
207
- items { ${LIST_FIELDS} }
208
- total
209
- }
210
- }
211
- `,
212
- variables: { filters, pagination: { page, limit }, search: search?.trim() || undefined, scope: this.scope },
213
- fetchPolicy: 'network-only'
214
- })
215
- if (response.errors?.length) throw response
216
- return { total: response.data.responses.total, records: response.data.responses.items }
217
- }
218
- }
package/client/route.ts DELETED
@@ -1,8 +0,0 @@
1
- /* The page this module brings to an application that puts it in its menu (ADR-0184 decision 8). */
2
- export default function route(page: string) {
3
- switch (page) {
4
- case 'inquiry-list':
5
- import('./pages/inquiry-list.js')
6
- return page
7
- }
8
- }
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "experimentalDecorators": true,
5
- "skipLibCheck": true,
6
- "strict": true,
7
- "declaration": true,
8
- "module": "esnext",
9
- "outDir": "../dist-client",
10
- "baseUrl": "./"
11
- },
12
- "include": ["./**/*"]
13
- }
@@ -1,103 +0,0 @@
1
- /*
2
- * The inquiry doors as the screens call them (ADR-0184). Every call throws the server's words when it is refused.
3
- */
4
- import gql from 'graphql-tag'
5
-
6
- import { client, uploadContext } from '@operato/graphql'
7
-
8
- export type FileRow = { id: string; name: string; fullpath: string; size?: number; mimetype?: string }
9
- export type Person = { id: string; name?: string | null } | null
10
-
11
- export interface InquiryReplyItem {
12
- id: string
13
- body?: string | null
14
- byHandler: boolean
15
- answer: boolean
16
- status?: string | null
17
- createdAt: string
18
- creator?: Person
19
- attachments?: FileRow[]
20
- }
21
-
22
- export interface InquiryItem {
23
- id: string
24
- kind: 'QUESTION' | 'BUG' | 'PROPOSAL' | string
25
- title: string
26
- body?: string | null
27
- status: string
28
- visibility: 'DOMAIN' | 'PRIVATE' | string
29
- pageUrl?: string | null
30
- appName?: string | null
31
- appVersion?: string | null
32
- replyCount: number
33
- createdAt: string
34
- answeredAt?: string | null
35
- closedAt?: string | null
36
- mine?: boolean
37
- isNew?: boolean
38
- canReply?: boolean
39
- asHandler?: boolean
40
- moves?: string[]
41
- creator?: Person
42
- assignee?: Person
43
- attachments?: FileRow[]
44
- replies?: InquiryReplyItem[]
45
- }
46
-
47
- export interface InquiryInput {
48
- kind: string
49
- title: string
50
- body?: string
51
- visibility?: string
52
- pageUrl?: string
53
- }
54
-
55
- export type InquirySummary = { newAnswers: number; unhandled: number; handler: boolean; badge: number }
56
-
57
- /** The fields a list row needs. */
58
- export const LIST_FIELDS = `id kind title status visibility replyCount createdAt mine isNew creator { id name } assignee { id name }`
59
-
60
- const FILE = `attachments { id name fullpath size mimetype }`
61
-
62
- /** Everything one inquiry shows. */
63
- export const FULL_FIELDS = `${LIST_FIELDS} body pageUrl appName appVersion answeredAt closedAt canReply asHandler moves ${FILE}
64
- replies { id body byHandler answer status createdAt creator { id name } ${FILE} }`
65
-
66
- function answer<T>(response: any, field: string): T {
67
- if (response.errors?.length) throw new Error(response.errors.map((one: any) => one.message).join(' · '))
68
- return response.data?.[field] as T
69
- }
70
-
71
- async function ask<T>(query: string, variables: Record<string, unknown>, field: string): Promise<T> {
72
- return answer<T>(await client.query({ query: gql(query), variables, fetchPolicy: 'network-only' }), field)
73
- }
74
-
75
- async function change<T>(mutation: string, variables: Record<string, unknown>, field: string, uploading?: (fraction: number) => void): Promise<T> {
76
- return answer<T>(await client.mutate({ mutation: gql(mutation), variables, ...(uploading ? { context: uploadContext(uploading) } : {}) }), field)
77
- }
78
-
79
- /** Tells the badge and an open list that an inquiry changed. */
80
- export const inquiriesChanged = () => window.dispatchEvent(new CustomEvent('inquiries-changed'))
81
-
82
- export const inquiryApi = {
83
- one: (id: string) => ask<InquiryItem | null>(`query ($id: String!) { inquiry(id: $id) { ${FULL_FIELDS} } }`, { id }, 'inquiry'),
84
- summary: () => ask<InquirySummary>(`query { inquirySummary { newAnswers unhandled handler badge } }`, {}, 'inquirySummary'),
85
- create: (input: InquiryInput, attachments: File[], uploading?: (fraction: number) => void) =>
86
- change<InquiryItem>(
87
- `mutation ($input: InquiryInput!, $attachments: [Upload!]) { createInquiry(input: $input, attachments: $attachments) { id } }`,
88
- { input, attachments: attachments.length ? attachments : null },
89
- 'createInquiry',
90
- attachments.length ? uploading : undefined
91
- ),
92
- reply: (id: string, body: string, asAnswer: boolean, attachments: File[], uploading?: (fraction: number) => void) =>
93
- change<InquiryItem>(
94
- `mutation ($id: String!, $body: String, $answer: Boolean, $attachments: [Upload!]) { replyInquiry(id: $id, body: $body, answer: $answer, attachments: $attachments) { id } }`,
95
- { id, body, answer: asAnswer, attachments: attachments.length ? attachments : null },
96
- 'replyInquiry',
97
- attachments.length ? uploading : undefined
98
- ),
99
- move: (id: string, status: string, reason?: string) =>
100
- change<InquiryItem>(`mutation ($id: String!, $status: String!, $reason: String) { moveInquiry(id: $id, status: $status, reason: $reason) { id } }`, { id, status, reason }, 'moveInquiry'),
101
- take: (id: string) => change<InquiryItem>(`mutation ($id: String!) { takeInquiry(id: $id) { id } }`, { id }, 'takeInquiry'),
102
- read: (id: string) => change<InquiryItem>(`mutation ($id: String!) { readInquiry(id: $id) { id } }`, { id }, 'readInquiry')
103
- }
@@ -1,46 +0,0 @@
1
- /*
2
- * The count the menu shows on 문의 · 제안 (ADR-0184 decision 8) - new words on one's own inquiries, and for a handler
3
- * those waiting on the handlers. An application puts `generateInquiryBadge` on its menu entry; the count is asked again
4
- * whenever an inquiry notice arrives or an inquiry changes on a screen.
5
- */
6
- import { inquiryApi } from './inquiry-api.js'
7
-
8
- let count: number | undefined
9
- /* One more each time the count is asked again - a badge that was busy drawing the last one still sees the next. */
10
- let version = 0
11
- const waiting = new Set<() => void>()
12
-
13
- /** Asks the server again and tells every badge. A count that could not be asked is not zero: it draws nothing. */
14
- export async function refreshInquiryBadge(): Promise<void> {
15
- try {
16
- count = (await inquiryApi.summary()).badge
17
- } catch {
18
- count = undefined
19
- }
20
- version++
21
- for (const wake of [...waiting]) wake()
22
- }
23
-
24
- let generation = 0
25
-
26
- /** One badge's counts. The menu asks for a new one when it draws again; the one before it then ends, as worklist's do. */
27
- export async function* generateInquiryBadge() {
28
- const mine = ++generation
29
- for (const wake of [...waiting]) wake()
30
- let seen = version
31
- yield count
32
- while (true) {
33
- if (seen === version) {
34
- await new Promise<void>(resolve => {
35
- const wake = () => {
36
- waiting.delete(wake)
37
- resolve()
38
- }
39
- waiting.add(wake)
40
- })
41
- }
42
- if (mine !== generation) return
43
- seen = version
44
- yield count
45
- }
46
- }
@@ -1,26 +0,0 @@
1
- /*
2
- * The words and marks of inquiries - one place for the list, the thread, the writing window and the to-do.
3
- */
4
- import { i18next } from '@operato/i18n'
5
-
6
- export const t = (key: string, options: Record<string, unknown> = {}) => String(i18next.t(key, options))
7
-
8
- export const KINDS = ['QUESTION', 'BUG', 'PROPOSAL']
9
- export const VISIBILITIES = ['DOMAIN', 'PRIVATE']
10
- export const STATUSES = ['RECEIVED', 'IN_PROGRESS', 'ANSWERED', 'PLANNED', 'DECLINED', 'CLOSED']
11
-
12
- export const kindWord = (code?: string | null) => (code ? t(`inquiry.kind.${code}`, { defaultValue: code }) : '')
13
- export const statusWord = (code?: string | null) => (code ? t(`inquiry.status.${code}`, { defaultValue: code }) : '')
14
- export const visibilityWord = (code?: string | null) => (code ? t(`inquiry.visibility.${code}`, { defaultValue: code }) : '')
15
-
16
- /** The icon of each kind. */
17
- export const KIND_ICONS: Record<string, string> = { QUESTION: 'help', BUG: 'bug_report', PROPOSAL: 'lightbulb' }
18
-
19
- /** A day and time as the reader's locale writes it. */
20
- export function when(value?: string | Date | null): string {
21
- if (!value) return ''
22
- return new Date(value).toLocaleString(i18next.language, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
23
- }
24
-
25
- /** The words of a refusal the server gave, or the fallback. */
26
- export const reasonOf = (error: unknown) => (error as Error)?.message || t('inquiry.text.failed')
@@ -1,18 +0,0 @@
1
- /*
2
- * Who may open an inquiry's file by its path - whoever may read the inquiry (ADR-0184 decision 5), a reply's file as
3
- * its inquiry's. attachment-base has already checked the file is of the request's domain.
4
- */
5
- import { registerAttachmentReader } from '@things-factory/attachment-base'
6
- import { getRepository } from '@things-factory/shell'
7
-
8
- import { InquiryReply } from './service/inquiry/inquiry'
9
- import { INQUIRY_REF_TYPE, INQUIRY_REPLY_REF_TYPE, visibleInquiry } from './service/inquiry/inquiry-service'
10
-
11
- export function registerInquiryFiles(): void {
12
- registerAttachmentReader(INQUIRY_REF_TYPE, async (attachment, context) => !!attachment.refBy && !!(await visibleInquiry(attachment.refBy, context)))
13
- registerAttachmentReader(INQUIRY_REPLY_REF_TYPE, async (attachment, context) => {
14
- if (!attachment.refBy) return false
15
- const reply = await getRepository(InquiryReply).findOneBy({ id: attachment.refBy })
16
- return !!reply?.inquiryId && !!(await visibleInquiry(reply.inquiryId, context))
17
- })
18
- }
package/server/index.ts DELETED
@@ -1,18 +0,0 @@
1
- export * from './service'
2
- export { INQUIRY_ACTIVITY, INQUIRY_HANDLING } from './inquiry-activity'
3
-
4
- import { registerRoleTask } from '@things-factory/auth-base'
5
- import { ActivityInstallations } from '@things-factory/worklist'
6
-
7
- import { registerInquiryFiles } from './attachment-reader'
8
- import { INQUIRY_ACTIVITY } from './inquiry-activity'
9
- import { ROLE_TASKS } from './role-tasks'
10
-
11
- for (const task of ROLE_TASKS) registerRoleTask(task)
12
- registerInquiryFiles()
13
-
14
- /*
15
- * Importing this file registers the handlers' to-do (ADR-0184 decision 7) - a bootstrap hook would be too late for
16
- * worklist to find it. Which domains have it is the application's call: it installs `INQUIRY_ACTIVITY`.
17
- */
18
- ActivityInstallations.installActivityTemplate(INQUIRY_ACTIVITY as any)
@@ -1,35 +0,0 @@
1
- /*
2
- * 문의 처리 (ADR-0184 decision 7) - not an approval: while an inquiry waits on the handlers, one case of this activity is
3
- * open, pooled on the roles that give inquiry handling. It shows the inquiry through this module's own element
4
- * (`inquiry-todo`), which opens it; answering, deciding or closing the inquiry closes the case.
5
- *
6
- * Data only. An application installs it into its domains (Office `REQUIRED_ACTIVITIES`).
7
- */
8
- export const INQUIRY_HANDLING = 'Inquiry Handling'
9
-
10
- export const INQUIRY_ACTIVITY = {
11
- name: INQUIRY_HANDLING,
12
- label: 'inquiry handling',
13
- description: 'Answering a question, a bug report or a feature proposal',
14
- guide: [
15
- 'while an inquiry waits on the handlers, one case is open for the roles that give inquiry handling - whoever of them picks it.',
16
- 'it opens the inquiry; answering it, deciding a proposal or closing it closes the case.',
17
- 'the writer replying to an answer opens a new case.'
18
- ],
19
- release: '1.0.0',
20
- provider: 'hatiolab.com',
21
- category: 'support',
22
- activityType: 'user',
23
- priority: 2,
24
- startingType: 'issue',
25
- multiple: 'single',
26
- uiType: 'custom-element',
27
- uiSource: 'inquiry-todo',
28
- searchKeys: [{ name: 'inquiryId', description: 'The inquiry', inputKey: 'inquiryId', tKey: 'field.subject' }],
29
- viewType: '',
30
- viewSource: '',
31
- model: [
32
- { name: 'inquiryId', description: 'The inquiry this to-do answers', tag: 'inquiryId', hidden: true, mandatory: true, inout: 'in', type: 'string', options: {}, unit: null, quantifier: [1], spec: {} }
33
- ],
34
- standardTime: 3 * 24 * 60 * 60
35
- }
@@ -1,13 +0,0 @@
1
- /*
2
- * The work this module names, for the seats applications build (ADR-0139 decision 11).
3
- *
4
- * Writing an inquiry, and reading one's own and the open ones, is no task: anyone signed in to the domain asks
5
- * (ADR-0184 decision 6). Taking, answering, deciding where one stands and reading the private ones is
6
- * `inquiry.answering`.
7
- *
8
- * Data only - `server/index.ts` registers it - so an application's tests read it without starting the framework.
9
- */
10
-
11
- import type { RoleTask } from '@things-factory/auth-base'
12
-
13
- export const ROLE_TASKS: RoleTask[] = [{ id: 'inquiry.answering', grants: [{ category: 'inquiry', axes: ['mutation'] }] }]
@@ -1,19 +0,0 @@
1
- /* EXPORT ENTITY TYPES */
2
- export * from './inquiry/inquiry'
3
- export * from './inquiry/inquiry-rules'
4
- export { INQUIRY_REF_TYPE, INQUIRY_REPLY_REF_TYPE, visibleInquiry } from './inquiry/inquiry-service'
5
-
6
- /* IMPORT ENTITIES AND RESOLVERS */
7
- import { entities as InquiryEntities, resolvers as InquiryResolvers } from './inquiry'
8
-
9
- export const entities = [
10
- /* ENTITIES */
11
- ...InquiryEntities
12
- ]
13
-
14
- export const schema = {
15
- resolverClasses: [
16
- /* RESOLVER CLASSES */
17
- ...InquiryResolvers
18
- ]
19
- }
@@ -1,5 +0,0 @@
1
- import { Inquiry, InquiryReply } from './inquiry'
2
- import { InquiryReplyResolver, InquiryResolver } from './inquiry-resolver'
3
-
4
- export const entities = [Inquiry, InquiryReply]
5
- export const resolvers = [InquiryResolver, InquiryReplyResolver]