mirror of
https://github.com/hcengineering/platform.git
synced 2026-10-01 05:55:09 +02:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Generated
-109
@@ -164,15 +164,6 @@ dependencies:
|
||||
'@rush-temp/cloud-transactor':
|
||||
specifier: file:./projects/cloud-transactor.tgz
|
||||
version: file:projects/cloud-transactor.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4)
|
||||
'@rush-temp/cloud-transactor-api':
|
||||
specifier: file:./projects/cloud-transactor-api.tgz
|
||||
version: file:projects/cloud-transactor-api.tgz(esbuild@0.24.2)(ts-node@10.9.2)
|
||||
'@rush-temp/cloud-transactor-api-demo':
|
||||
specifier: file:./projects/cloud-transactor-api-demo.tgz
|
||||
version: file:projects/cloud-transactor-api-demo.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(esbuild@0.24.2)(ts-node@10.9.2)(utf-8-validate@6.0.4)
|
||||
'@rush-temp/cloud-transactor-http-api':
|
||||
specifier: file:./projects/cloud-transactor-http-api.tgz
|
||||
version: file:projects/cloud-transactor-http-api.tgz(bufferutil@4.0.8)(esbuild@0.24.2)(ts-node@10.9.2)(utf-8-validate@6.0.4)
|
||||
'@rush-temp/collaboration':
|
||||
specifier: file:./projects/collaboration.tgz
|
||||
version: file:projects/collaboration.tgz(esbuild@0.24.2)(ts-node@10.9.2)
|
||||
@@ -23038,106 +23029,6 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
file:projects/cloud-transactor-api-demo.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(esbuild@0.24.2)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-jDFW5bSJFL4PuJt0ESJ50jyMHtQKSd9eLfjoUXslmIUdAEITbEEz8N0cstMBujEybfmSghSi6haE2GnG/p4rHg==, tarball: file:projects/cloud-transactor-api-demo.tgz}
|
||||
id: file:projects/cloud-transactor-api-demo.tgz
|
||||
name: '@rush-temp/cloud-transactor-api-demo'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@cloudflare/workers-types': 4.20241022.0
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
itty-router: 5.0.18
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.24.2)(jest@29.7.0)(typescript@5.6.2)
|
||||
typescript: 5.6.2
|
||||
wrangler: 3.97.0(@cloudflare/workers-types@4.20241022.0)(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- bufferutil
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
file:projects/cloud-transactor-api.tgz(esbuild@0.24.2)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-KdCN6yGHiBNfGsSWtSwQuIfOQGGr0P40cj5u8Z5p5AQz0MbeoRJ1gFnpzI8VJjOhKfNKn3JR8Zdwg+JTq29yWQ==, tarball: file:projects/cloud-transactor-api.tgz}
|
||||
id: file:projects/cloud-transactor-api.tgz
|
||||
name: '@rush-temp/cloud-transactor-api'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.24.2)(jest@29.7.0)(typescript@5.6.2)
|
||||
typescript: 5.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/cloud-transactor-http-api.tgz(bufferutil@4.0.8)(esbuild@0.24.2)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-4vV2jdYPYycGKl0jomdYmnEvZgancPc2pdhJ6khPP3qR4FZFhHx5lF/uLuREoZ3d7GuQqNs4J+fKL/+iriCkaA==, tarball: file:projects/cloud-transactor-http-api.tgz}
|
||||
id: file:projects/cloud-transactor-http-api.tgz
|
||||
name: '@rush-temp/cloud-transactor-http-api'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@cloudflare/workers-types': 4.20241022.0
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
itty-router: 5.0.18
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.24.2)(jest@29.7.0)(typescript@5.6.2)
|
||||
typescript: 5.6.2
|
||||
wrangler: 3.97.0(@cloudflare/workers-types@4.20241022.0)(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- bufferutil
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
file:projects/cloud-transactor.tgz(@types/node@20.11.19)(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-6kBv/3KfTsOvsDrfVXQvQwtLM72ltx4oPHc3cJ9xtG82XLeQLLpeJmCy6CNDVOtXaqnagwF5TKm/zCw4sc/dFQ==, tarball: file:projects/cloud-transactor.tgz}
|
||||
id: file:projects/cloud-transactor.tgz
|
||||
|
||||
@@ -21,6 +21,7 @@ import core, {
|
||||
TxProcessor,
|
||||
getCurrentAccount,
|
||||
reduceCalls,
|
||||
type Account,
|
||||
type AnyAttribute,
|
||||
type ArrOf,
|
||||
type AttachedDoc,
|
||||
@@ -253,6 +254,18 @@ export function getClient (): TxOperations & Client {
|
||||
return clientProxy
|
||||
}
|
||||
|
||||
export type OnClientListener = (client: Client, account: Account) => void
|
||||
const onClientListeners: OnClientListener[] = []
|
||||
|
||||
export function onClient (l: OnClientListener): void {
|
||||
onClientListeners.push(l)
|
||||
if (client !== undefined) {
|
||||
setTimeout(() => {
|
||||
l(client, getCurrentAccount())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let txQueue: Tx[] = []
|
||||
|
||||
export type RefreshListener = () => void
|
||||
@@ -306,6 +319,10 @@ export async function setClient (_client: Client): Promise<void> {
|
||||
if (needRefresh || globalQueries.length > 0) {
|
||||
await refreshClient(true)
|
||||
}
|
||||
const acc = getCurrentAccount()
|
||||
onClientListeners.forEach((l) => {
|
||||
l(_client, acc)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
|
||||
@@ -14,31 +14,21 @@
|
||||
//
|
||||
import activity, { type ActivityMessage, type SavedMessage } from '@hcengineering/activity'
|
||||
import core, { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, onClient } from '@hcengineering/presentation'
|
||||
import { writable } from 'svelte/store'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
|
||||
export const savedMessagesStore = writable<Array<WithLookup<SavedMessage>>>([])
|
||||
export const messageInFocus = writable<Ref<ActivityMessage> | undefined>(undefined)
|
||||
|
||||
const savedMessagesQuery = createQuery(true)
|
||||
|
||||
export function loadSavedMessages (): void {
|
||||
const client = getClient()
|
||||
|
||||
if (client !== undefined) {
|
||||
savedMessagesQuery.query(
|
||||
activity.class.SavedMessage,
|
||||
{ space: core.space.Workspace },
|
||||
(res) => {
|
||||
savedMessagesStore.set(res.filter(({ $lookup }) => $lookup?.attachedTo !== undefined))
|
||||
},
|
||||
{ lookup: { attachedTo: activity.class.ActivityMessage }, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
loadSavedMessages()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
loadSavedMessages()
|
||||
onClient(() => {
|
||||
savedMessagesQuery.query(
|
||||
activity.class.SavedMessage,
|
||||
{ space: core.space.Workspace },
|
||||
(res) => {
|
||||
savedMessagesStore.set(res.filter(({ $lookup }) => $lookup?.attachedTo !== undefined))
|
||||
},
|
||||
{ lookup: { attachedTo: activity.class.ActivityMessage }, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
type ReccuringInstance,
|
||||
generateEventId
|
||||
} from '@hcengineering/calendar'
|
||||
import { type IdMap, type Timestamp, getCurrentAccount, toIdMap, type DocumentUpdate } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { showPopup, closePopup, DAY } from '@hcengineering/ui'
|
||||
import { type DocumentUpdate, type IdMap, type Timestamp, getCurrentAccount, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery, getClient, onClient } from '@hcengineering/presentation'
|
||||
import { closePopup, DAY, showPopup } from '@hcengineering/ui'
|
||||
import { writable } from 'svelte/store'
|
||||
import calendar from './plugin'
|
||||
import UpdateRecInstancePopup from './components/UpdateRecInstancePopup.svelte'
|
||||
import calendar from './plugin'
|
||||
|
||||
export function saveUTC (date: Timestamp): Timestamp {
|
||||
const utcdate = new Date(date)
|
||||
@@ -74,24 +74,14 @@ export const calendarByIdStore = writable<IdMap<Calendar>>(new Map())
|
||||
export const calendarStore = writable<Calendar[]>([])
|
||||
export const visibleCalendarStore = writable<Calendar[]>([])
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
|
||||
if (client !== undefined) {
|
||||
const query = createQuery(true)
|
||||
query.query(calendar.class.Calendar, {}, (res) => {
|
||||
calendarStore.set(res)
|
||||
visibleCalendarStore.set(res.filter((p) => !p.hidden))
|
||||
calendarByIdStore.set(toIdMap(res))
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
fillStores()
|
||||
const query = createQuery(true)
|
||||
onClient((client, account) => {
|
||||
query.query(calendar.class.Calendar, {}, (res) => {
|
||||
calendarStore.set(res)
|
||||
visibleCalendarStore.set(res.filter((p) => !p.hidden))
|
||||
calendarByIdStore.set(toIdMap(res))
|
||||
})
|
||||
})
|
||||
|
||||
export async function updatePast (ops: DocumentUpdate<Event>, object: ReccuringInstance): Promise<void> {
|
||||
const client = getClient()
|
||||
|
||||
@@ -28,7 +28,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import notification, { type DocNotifyContext } from '@hcengineering/notification'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, MessageBox, onClient } from '@hcengineering/presentation'
|
||||
import { type Action, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import workbench, { type SpecialNavModel } from '@hcengineering/workbench'
|
||||
@@ -364,12 +364,9 @@ function archiveActivityChannels (contexts: DocNotifyContext[]): void {
|
||||
)
|
||||
}
|
||||
|
||||
const savedAttachmentsQuery = createQuery(true)
|
||||
export function loadSavedAttachments (): void {
|
||||
const client = getClient()
|
||||
|
||||
if (client !== undefined) {
|
||||
const savedAttachmentsQuery = createQuery(true)
|
||||
|
||||
onClient(() => {
|
||||
savedAttachmentsQuery.query(
|
||||
attachment.class.SavedAttachments,
|
||||
{ space: core.space.Workspace },
|
||||
@@ -378,11 +375,7 @@ export function loadSavedAttachments (): void {
|
||||
},
|
||||
{ lookup: { attachedTo: attachment.class.Attachment }, sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
loadSavedAttachments()
|
||||
}, 50)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function hideActivityChannels (contexts: DocNotifyContext[]): Promise<void> {
|
||||
|
||||
@@ -52,7 +52,7 @@ import core, {
|
||||
} from '@hcengineering/core'
|
||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||
import { type IntlString, getEmbeddedLabel, getResource, translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, onClient } from '@hcengineering/presentation'
|
||||
import { type TemplateDataProvider } from '@hcengineering/templates'
|
||||
import {
|
||||
getCurrentResolvedLocation,
|
||||
@@ -323,42 +323,32 @@ export const personByIdStore = derived([personAccountPersonByIdStore, employeeBy
|
||||
return new Map([...m1, ...m2])
|
||||
})
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
const query = createQuery(true)
|
||||
const accountQ = createQuery(true)
|
||||
const accountPersonQuery = createQuery(true)
|
||||
const providerQuery = createQuery(true)
|
||||
|
||||
if (client !== undefined) {
|
||||
const accountPersonQuery = createQuery(true)
|
||||
onClient(() => {
|
||||
query.query(contact.mixin.Employee, { active: { $in: [true, false] } }, (res) => {
|
||||
employeesStore.set(res)
|
||||
employeeByIdStore.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const query = createQuery(true)
|
||||
query.query(contact.mixin.Employee, { active: { $in: [true, false] } }, (res) => {
|
||||
employeesStore.set(res)
|
||||
employeeByIdStore.set(toIdMap(res))
|
||||
accountQ.query(contact.class.PersonAccount, {}, (res) => {
|
||||
personAccountByIdStore.set(toIdMap(res))
|
||||
|
||||
const persons = res.map((it) => it.person)
|
||||
|
||||
accountPersonQuery.query<Person>(contact.class.Person, { _id: { $in: persons } }, (res) => {
|
||||
const personIn = toIdMap(res)
|
||||
personAccountPersonByIdStore.set(personIn)
|
||||
})
|
||||
})
|
||||
|
||||
const accountQ = createQuery(true)
|
||||
accountQ.query(contact.class.PersonAccount, {}, (res) => {
|
||||
personAccountByIdStore.set(toIdMap(res))
|
||||
|
||||
const persons = res.map((it) => it.person)
|
||||
|
||||
accountPersonQuery.query<Person>(contact.class.Person, { _id: { $in: persons } }, (res) => {
|
||||
const personIn = toIdMap(res)
|
||||
personAccountPersonByIdStore.set(personIn)
|
||||
})
|
||||
})
|
||||
|
||||
const providerQuery = createQuery(true)
|
||||
providerQuery.query(contact.class.ChannelProvider, {}, (res) => {
|
||||
channelProviders.set(res)
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
fillStores()
|
||||
providerQuery.query(contact.class.ChannelProvider, {}, (res) => {
|
||||
channelProviders.set(res)
|
||||
})
|
||||
})
|
||||
|
||||
const userStatusesQuery = createQuery(true)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type Room,
|
||||
type MeetingMinutes
|
||||
} from '@hcengineering/love'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, onClient } from '@hcengineering/presentation'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import { personIdByAccountId } from '@hcengineering/contact-resources'
|
||||
import aiBot from '@hcengineering/ai-bot'
|
||||
@@ -79,77 +79,69 @@ function filterParticipantInfo (value: ParticipantInfo[]): ParticipantInfo[] {
|
||||
|
||||
export const storePromise = writable<Promise<void>>(new Promise((resolve) => {}))
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
if (client !== undefined || getCurrentAccount() != null) {
|
||||
const query = createQuery(true)
|
||||
const roomPromise = new Promise<void>((resolve) =>
|
||||
query.query(love.class.Room, {}, (res) => {
|
||||
rooms.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const statusQuery = createQuery(true)
|
||||
const infoPromise = new Promise<void>((resolve) =>
|
||||
statusQuery.query(love.class.ParticipantInfo, {}, (res) => {
|
||||
infos.set(filterParticipantInfo(res))
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const floorsQuery = createQuery(true)
|
||||
const floorPromise = new Promise<void>((resolve) =>
|
||||
floorsQuery.query(love.class.Floor, {}, (res) => {
|
||||
floors.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const requestsQuery = createQuery(true)
|
||||
const requestPromise = new Promise<void>((resolve) =>
|
||||
requestsQuery.query(
|
||||
love.class.JoinRequest,
|
||||
{ person: (getCurrentAccount() as PersonAccount).person, status: RequestStatus.Pending },
|
||||
(res) => {
|
||||
myRequests.set(res)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
)
|
||||
const preferencesQuery = createQuery(true)
|
||||
const preferencePromise = new Promise<void>((resolve) =>
|
||||
preferencesQuery.query(love.class.DevicesPreference, {}, (res) => {
|
||||
myPreferences.set(res[0])
|
||||
$myPreferences = res[0]
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const invitesQuery = createQuery(true)
|
||||
const invitesPromise = new Promise<void>((resolve) =>
|
||||
invitesQuery.query(love.class.Invite, { status: RequestStatus.Pending }, (res) => {
|
||||
invites.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
storePromise.set(
|
||||
new Promise((resolve) => {
|
||||
void Promise.all([
|
||||
roomPromise,
|
||||
infoPromise,
|
||||
floorPromise,
|
||||
requestPromise,
|
||||
preferencePromise,
|
||||
invitesPromise
|
||||
]).then(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
const query = createQuery(true)
|
||||
const statusQuery = createQuery(true)
|
||||
const floorsQuery = createQuery(true)
|
||||
const requestsQuery = createQuery(true)
|
||||
const preferencesQuery = createQuery(true)
|
||||
const invitesQuery = createQuery(true)
|
||||
|
||||
fillStores()
|
||||
onClient(() => {
|
||||
const roomPromise = new Promise<void>((resolve) =>
|
||||
query.query(love.class.Room, {}, (res) => {
|
||||
rooms.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const infoPromise = new Promise<void>((resolve) =>
|
||||
statusQuery.query(love.class.ParticipantInfo, {}, (res) => {
|
||||
infos.set(filterParticipantInfo(res))
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const floorPromise = new Promise<void>((resolve) =>
|
||||
floorsQuery.query(love.class.Floor, {}, (res) => {
|
||||
floors.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const requestPromise = new Promise<void>((resolve) =>
|
||||
requestsQuery.query(
|
||||
love.class.JoinRequest,
|
||||
{ person: (getCurrentAccount() as PersonAccount).person, status: RequestStatus.Pending },
|
||||
(res) => {
|
||||
myRequests.set(res)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
)
|
||||
const preferencePromise = new Promise<void>((resolve) =>
|
||||
preferencesQuery.query(love.class.DevicesPreference, {}, (res) => {
|
||||
myPreferences.set(res[0])
|
||||
$myPreferences = res[0]
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
|
||||
const invitesPromise = new Promise<void>((resolve) =>
|
||||
invitesQuery.query(love.class.Invite, { status: RequestStatus.Pending }, (res) => {
|
||||
invites.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
storePromise.set(
|
||||
new Promise((resolve) => {
|
||||
void Promise.all([
|
||||
roomPromise,
|
||||
infoPromise,
|
||||
floorPromise,
|
||||
requestPromise,
|
||||
preferencePromise,
|
||||
invitesPromise
|
||||
]).then(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
export const lockedRoom = writable<string>('')
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
getCurrentAccount,
|
||||
toIdMap,
|
||||
type Attribute,
|
||||
type Class,
|
||||
@@ -27,7 +29,7 @@ import core, {
|
||||
type TxOperations
|
||||
} from '@hcengineering/core'
|
||||
import { type IntlString, type Resources } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, onClient } from '@hcengineering/presentation'
|
||||
import task, {
|
||||
getStatusIndex,
|
||||
makeRank,
|
||||
@@ -41,9 +43,6 @@ import { getCurrentLocation, navigate, showPopup } from '@hcengineering/ui'
|
||||
import { type ViewletDescriptor } from '@hcengineering/view'
|
||||
import { CategoryQuery, groupBy, statusStore } from '@hcengineering/view-resources'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
|
||||
import AssignedTasks from './components/AssignedTasks.svelte'
|
||||
import Dashboard from './components/Dashboard.svelte'
|
||||
@@ -69,14 +68,14 @@ import TaskKindSelector from './components/taskTypes/TaskKindSelector.svelte'
|
||||
import TaskTypeClassPresenter from './components/taskTypes/TaskTypeClassPresenter.svelte'
|
||||
import TaskTypePresenter from './components/taskTypes/TaskTypePresenter.svelte'
|
||||
|
||||
import ProjectTypeSelector from './components/projectTypes/ProjectTypeSelector.svelte'
|
||||
import { employeeByIdStore, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import CreateProjectType from './components/projectTypes/CreateProjectType.svelte'
|
||||
import ProjectTypeGeneralSectionEditor from './components/projectTypes/ProjectTypeGeneralSectionEditor.svelte'
|
||||
import ProjectTypeTasksTypeSectionEditor from './components/projectTypes/ProjectTypeTasksTypeSectionEditor.svelte'
|
||||
import ProjectTypeAutomationsSectionEditor from './components/projectTypes/ProjectTypeAutomationsSectionEditor.svelte'
|
||||
import ProjectTypeCollectionsSectionEditor from './components/projectTypes/ProjectTypeCollectionsSectionEditor.svelte'
|
||||
import ProjectTypeGeneralSectionEditor from './components/projectTypes/ProjectTypeGeneralSectionEditor.svelte'
|
||||
import ProjectTypeSelector from './components/projectTypes/ProjectTypeSelector.svelte'
|
||||
import ProjectTypeTasksTypeSectionEditor from './components/projectTypes/ProjectTypeTasksTypeSectionEditor.svelte'
|
||||
import TaskTypeEditor from './components/taskTypes/TaskTypeEditor.svelte'
|
||||
import { employeeByIdStore, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
|
||||
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
|
||||
export { default as TypeSelector } from './components/TypeSelector.svelte'
|
||||
@@ -364,33 +363,24 @@ export const taskTypeStore = writable<IdMap<TaskType>>(new Map())
|
||||
export const typesOfJoinedProjectsStore = writable<Array<Ref<ProjectType>>>()
|
||||
export const joinedProjectsStore = writable<Project[]>()
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
const query = createQuery(true)
|
||||
const taskQuery = createQuery(true)
|
||||
const projectQuery = createQuery(true)
|
||||
|
||||
if (client !== undefined && getCurrentAccount() != null) {
|
||||
const query = createQuery(true)
|
||||
query.query(task.class.ProjectType, {}, (res) => {
|
||||
typeStore.set(toIdMap(res))
|
||||
})
|
||||
onClient((client, user) => {
|
||||
query.query(task.class.ProjectType, {}, (res) => {
|
||||
typeStore.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const taskQuery = createQuery(true)
|
||||
taskQuery.query(task.class.TaskType, {}, (res) => {
|
||||
taskTypeStore.set(toIdMap(res))
|
||||
})
|
||||
taskQuery.query(task.class.TaskType, {}, (res) => {
|
||||
taskTypeStore.set(toIdMap(res))
|
||||
})
|
||||
|
||||
const projectQuery = createQuery(true)
|
||||
projectQuery.query(task.class.Project, { members: getCurrentAccount()._id }, (res) => {
|
||||
typesOfJoinedProjectsStore.set(res.map((r) => r.type).filter((it, idx, arr) => arr.indexOf(it) === idx))
|
||||
joinedProjectsStore.set(res)
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
fillStores()
|
||||
projectQuery.query(task.class.Project, { members: user._id }, (res) => {
|
||||
typesOfJoinedProjectsStore.set(res.map((r) => r.type).filter((it, idx, arr) => arr.indexOf(it) === idx))
|
||||
joinedProjectsStore.set(res)
|
||||
})
|
||||
})
|
||||
|
||||
export const selectedTypeStore = writable<Ref<ProjectType> | undefined>(undefined)
|
||||
export const selectedTaskTypeStore = writable<Ref<TaskType> | undefined>(undefined)
|
||||
|
||||
@@ -36,7 +36,7 @@ import core, {
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient, onClient } from '@hcengineering/presentation'
|
||||
import task, { getStatusIndex, makeRank, type ProjectType } from '@hcengineering/task'
|
||||
import { activeProjects as taskActiveProjects, taskTypeStore } from '@hcengineering/task-resources'
|
||||
import {
|
||||
@@ -577,36 +577,25 @@ export interface IssueRef {
|
||||
export type IssueReverseRevMap = Map<Ref<Doc>, IssueRef[]>
|
||||
export const relatedIssues = writable<IssueReverseRevMap>(new Map())
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
|
||||
if (client !== undefined) {
|
||||
const relatedIssuesQuery = createQuery(true)
|
||||
|
||||
relatedIssuesQuery.query(
|
||||
tracker.class.Issue,
|
||||
{ 'relations._id': { $exists: true } },
|
||||
(res) => {
|
||||
const nMap: IssueReverseRevMap = new Map()
|
||||
for (const r of res) {
|
||||
for (const rr of r.relations ?? []) {
|
||||
nMap.set(rr._id, [...(nMap.get(rr._id) ?? []), { _id: r._id, status: r.status }])
|
||||
}
|
||||
}
|
||||
relatedIssues.set(nMap)
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
relations: 1,
|
||||
status: 1
|
||||
const relatedIssuesQuery = createQuery(true)
|
||||
onClient(() => {
|
||||
relatedIssuesQuery.query(
|
||||
tracker.class.Issue,
|
||||
{ 'relations._id': { $exists: true } },
|
||||
(res) => {
|
||||
const nMap: IssueReverseRevMap = new Map()
|
||||
for (const r of res) {
|
||||
for (const rr of r.relations ?? []) {
|
||||
nMap.set(rr._id, [...(nMap.get(rr._id) ?? []), { _id: r._id, status: r.status }])
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
fillStores()
|
||||
relatedIssues.set(nMap)
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
relations: 1,
|
||||
status: 1
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import core, { type IdMap, type Status, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { createQuery, onClient } from '@hcengineering/presentation'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
interface Store {
|
||||
@@ -28,23 +28,13 @@ export const statusStore = writable<Store>({
|
||||
array: []
|
||||
})
|
||||
|
||||
function fillStores (): void {
|
||||
const client = getClient()
|
||||
|
||||
if (client !== undefined) {
|
||||
const query = createQuery(true)
|
||||
query.query(core.class.Status, {}, (res) => {
|
||||
const obj = {
|
||||
byId: toIdMap(res),
|
||||
array: res
|
||||
}
|
||||
statusStore.set(obj)
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
fillStores()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
fillStores()
|
||||
const query = createQuery(true)
|
||||
onClient(() => {
|
||||
query.query(core.class.Status, {}, (res) => {
|
||||
const obj = {
|
||||
byId: toIdMap(res),
|
||||
array: res
|
||||
}
|
||||
statusStore.set(obj)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2230,21 +2230,6 @@
|
||||
"packageName": "@hcengineering/cloud-transactor",
|
||||
"projectFolder": "workers/transactor",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/cloud-transactor-api",
|
||||
"projectFolder": "workers/transactor-api",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/cloud-transactor-api-demo",
|
||||
"projectFolder": "workers/transactor-api-demo",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/cloud-transactor-http-api",
|
||||
"projectFolder": "workers/transactor-http-api",
|
||||
"shouldPublish": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "@hcengineering/cloud-transactor-api-demo",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"template": "cloud",
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev --port 4050",
|
||||
"dev-local": "wrangler dev --port 4050 --local --upstream-protocol=http",
|
||||
"start": "wrangler dev --port 4050",
|
||||
"cf-typegen": "wrangler types",
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"test": "jest --passWithNoTests --silent --forceExit",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241022.0",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3",
|
||||
"wrangler": "^3.97.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/cloud-transactor-api": "^0.6.0",
|
||||
"itty-router": "^5.0.18"
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
type ConnectOptions,
|
||||
type TransactorRawApi,
|
||||
type TransactorService,
|
||||
createHttpClient,
|
||||
createRpcClient,
|
||||
getWorkspaceLogin,
|
||||
unpackModel
|
||||
} from '@hcengineering/cloud-transactor-api'
|
||||
import contact, { AvatarType, type Person } from '@hcengineering/contact'
|
||||
import core, { type AccountClient, type Ref, type TxCreateDoc, TxOperations, generateId } from '@hcengineering/core'
|
||||
import { Router, error, json } from 'itty-router'
|
||||
|
||||
async function callClient<T> (client: T, method: () => Promise<any>): Promise<Response> {
|
||||
try {
|
||||
return json(await method())
|
||||
} catch (error) {
|
||||
console.error({ error })
|
||||
throw error
|
||||
} finally {
|
||||
if (Symbol.dispose in (client as any)) {
|
||||
;(client as any)[Symbol.dispose]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch (request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
const transactorService = env.TRANSACTOR_SERVICE as any as TransactorService
|
||||
|
||||
function getConnectOpts (params: Record<string, any>): ConnectOptions {
|
||||
return {
|
||||
authOptions: {
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
workspace: params.workspace
|
||||
},
|
||||
serverConfig: {
|
||||
ACCOUNTS_URL: env.ACCOUNTS_URL
|
||||
},
|
||||
workspaceId: params.workspace,
|
||||
loadModel: params.loadModel === true
|
||||
}
|
||||
}
|
||||
|
||||
async function rawClient (params: Record<string, any>): Promise<TransactorRawApi> {
|
||||
const email = params.email
|
||||
const password = params.password
|
||||
const workspace = params.workspace
|
||||
const info = await getWorkspaceLogin('', { email, password, workspace }, { ACCOUNTS_URL: env.ACCOUNTS_URL })
|
||||
return await transactorService.openRpc(info.token, params.workspace)
|
||||
}
|
||||
|
||||
async function rpcClient (params: Record<string, any>): Promise<AccountClient> {
|
||||
return await createRpcClient(transactorService, getConnectOpts(params))
|
||||
}
|
||||
|
||||
async function httpClient (params: Record<string, any>): Promise<AccountClient> {
|
||||
return await createHttpClient(env.HTTP_API_URL, getConnectOpts(params))
|
||||
}
|
||||
|
||||
const router = Router()
|
||||
router
|
||||
.get('/demo-find-raw/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rawClient(params)
|
||||
return await callClient(client, async () => {
|
||||
return await client.findAll(contact.class.Person, {})
|
||||
})
|
||||
})
|
||||
.get('/demo-find-rpc/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rpcClient(params)
|
||||
return await callClient(client, async () => {
|
||||
return await client.findAll(contact.class.Person, {})
|
||||
})
|
||||
})
|
||||
.get('/demo-find-http/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await httpClient(params)
|
||||
return await callClient(client, async () => {
|
||||
return await client.findAll(contact.class.Person, {})
|
||||
})
|
||||
})
|
||||
.get('/demo-get-model-raw/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rawClient(params)
|
||||
return await callClient(client, async () => {
|
||||
return await unpackModel(await client.getModel())
|
||||
})
|
||||
})
|
||||
.get('/demo-get-model-rpc/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rpcClient({ ...params, loadModel: true })
|
||||
return await callClient(client, async () => {
|
||||
return client.getModel()
|
||||
})
|
||||
})
|
||||
.get('/demo-get-model-http/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await httpClient({ ...params, loadModel: true })
|
||||
return await callClient(client, async () => {
|
||||
return client.getModel()
|
||||
})
|
||||
})
|
||||
.get('/demo-tx-raw/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rawClient(params)
|
||||
return await callClient(client, async () => {
|
||||
const account = await client.getAccount()
|
||||
const id = generateId()
|
||||
const tx: TxCreateDoc<Person> = {
|
||||
_id: id as Ref<TxCreateDoc<Person>>,
|
||||
_class: core.class.TxCreateDoc,
|
||||
space: core.space.Tx,
|
||||
objectId: id as Ref<Person>,
|
||||
objectClass: contact.class.Person,
|
||||
objectSpace: account.space,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: account._id,
|
||||
createdBy: account._id,
|
||||
attributes: {
|
||||
name: 'Person ' + id,
|
||||
city: 'Unknown',
|
||||
avatarType: AvatarType.COLOR
|
||||
}
|
||||
}
|
||||
return await client.tx(tx)
|
||||
})
|
||||
})
|
||||
.get('/demo-tx-rpc/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await rpcClient({ ...params, loadModel: true })
|
||||
return await callClient(client, async () => {
|
||||
const account = await client.getAccount()
|
||||
const txops = new TxOperations(client, account._id)
|
||||
return await txops.createDoc(contact.class.Person, account.space, {
|
||||
name: 'Person ' + generateId(),
|
||||
city: 'Unknown',
|
||||
avatarType: AvatarType.COLOR
|
||||
})
|
||||
})
|
||||
})
|
||||
.get('/demo-tx-http/:email/:password/:workspace', async ({ params }) => {
|
||||
const client = await httpClient({ ...params, loadModel: true })
|
||||
return await callClient(client, async () => {
|
||||
const account = await client.getAccount()
|
||||
const txops = new TxOperations(client, account._id)
|
||||
return await txops.createDoc(contact.class.Person, account.space, {
|
||||
name: 'Person ' + generateId(),
|
||||
city: 'Unknown',
|
||||
avatarType: AvatarType.COLOR
|
||||
})
|
||||
})
|
||||
})
|
||||
.all('*', () => error(404))
|
||||
return await router.fetch(request).catch(error)
|
||||
}
|
||||
} satisfies ExportedHandler<Env>
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo",
|
||||
"types": ["@cloudflare/workers-types", "jest"]
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// Generated by Wrangler by running `wrangler types`
|
||||
|
||||
interface Env {
|
||||
ACCOUNTS_URL: "http://127.0.0.1:3000";
|
||||
HTTP_API_URL: "http://127.0.0.1:4040";
|
||||
TRANSACTOR_SERVICE: Fetcher;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "cloud-transactor-api-demo"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-09-23"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
keep_vars = true
|
||||
|
||||
[vars]
|
||||
ACCOUNTS_URL = "http://127.0.0.1:3000"
|
||||
HTTP_API_URL = "http://127.0.0.1:4040"
|
||||
|
||||
[[services]]
|
||||
binding = "TRANSACTOR_SERVICE"
|
||||
service = "cloud-transactor"
|
||||
entrypoint = "TransactorRpc"
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/node/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "node"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "@hcengineering/cloud-transactor-api",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"template": "node",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"test": "jest --passWithNoTests --silent --forceExit",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32"
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { concatLink } from '@hcengineering/core'
|
||||
|
||||
/**
|
||||
* Configuration options for password-based authentication
|
||||
* @public
|
||||
*/
|
||||
export interface PasswordAuthOptions {
|
||||
/** User's email address */
|
||||
email: string
|
||||
|
||||
/** User's password */
|
||||
password: string
|
||||
|
||||
/** Workspace name */
|
||||
workspace: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for token-based authentication
|
||||
* @public
|
||||
*/
|
||||
export interface TokenAuthOptions {
|
||||
/** Authentication token */
|
||||
token: string
|
||||
|
||||
/** Workspace name */
|
||||
workspace: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type representing all authentication options
|
||||
* Can be either password-based or token-based authentication
|
||||
* @public
|
||||
*/
|
||||
export type AuthOptions = PasswordAuthOptions | TokenAuthOptions
|
||||
|
||||
/** @public */
|
||||
export interface LoginInfo {
|
||||
token: string
|
||||
endpoint: string
|
||||
confirmed: boolean
|
||||
email: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
workspace: string
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
ACCOUNTS_URL: string
|
||||
}
|
||||
|
||||
async function loadServerConfig (url: string): Promise<ServerConfig> {
|
||||
const configUrl = concatLink(url, '/config.json')
|
||||
const res = await fetch(configUrl)
|
||||
if (res.ok) {
|
||||
return (await res.json()) as ServerConfig
|
||||
}
|
||||
throw new Error('Failed to fetch config')
|
||||
}
|
||||
|
||||
export async function getWorkspaceLogin (
|
||||
configUrl: string,
|
||||
options: AuthOptions,
|
||||
serverConfig?: ServerConfig
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
serverConfig ??= await loadServerConfig(configUrl)
|
||||
|
||||
let token: string
|
||||
|
||||
if ('token' in options) {
|
||||
token = options.token
|
||||
} else {
|
||||
const { email, password, workspace } = options
|
||||
token = await login(serverConfig.ACCOUNTS_URL, email, password, workspace)
|
||||
}
|
||||
|
||||
if (token === undefined) {
|
||||
throw new Error('Login failed')
|
||||
}
|
||||
|
||||
const ws = await selectWorkspace(serverConfig.ACCOUNTS_URL, token, options.workspace)
|
||||
if (ws === undefined) {
|
||||
throw new Error('Workspace not found')
|
||||
}
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
async function login (accountsUrl: string, user: string, password: string, workspace: string): Promise<string> {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'login',
|
||||
params: [user, password, workspace]
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
return result.result?.token
|
||||
}
|
||||
|
||||
async function selectWorkspace (accountsUrl: string, token: string, workspace: string): Promise<WorkspaceLoginInfo> {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'selectWorkspace',
|
||||
params: [workspace, 'external']
|
||||
})
|
||||
})
|
||||
const result = await response.json()
|
||||
return result.result as WorkspaceLoginInfo
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
type Account,
|
||||
type AccountClient,
|
||||
type Class,
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
type FindOptions,
|
||||
type FindResult,
|
||||
type ModelDb,
|
||||
type Tx,
|
||||
type TxResult,
|
||||
type Ref,
|
||||
type Hierarchy,
|
||||
type SearchQuery,
|
||||
type SearchOptions,
|
||||
type SearchResult,
|
||||
type WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import { type ConnectOptions } from './types'
|
||||
import { getWorkspaceLogin } from './account'
|
||||
import { decodeModel } from './utils'
|
||||
|
||||
export async function createHttpClient (httpApiWorkerUrl: string, options: ConnectOptions): Promise<AccountClient> {
|
||||
let token = options.workspaceToken
|
||||
if (token === undefined) {
|
||||
if (options.authOptions === undefined) {
|
||||
throw new Error('Either workspaceToken or authOptions must be provided')
|
||||
}
|
||||
if (options.configUrl === '' && options.serverConfig === undefined) {
|
||||
throw new Error('Either configUrl or serverConfig must be provided')
|
||||
}
|
||||
const ws = await getWorkspaceLogin(options.configUrl ?? '', options.authOptions, options.serverConfig)
|
||||
token = ws.token
|
||||
}
|
||||
const client = new TransactorHttpClient(token, options.workspaceId ?? '', httpApiWorkerUrl)
|
||||
if (options.loadModel ?? false) {
|
||||
await client.loadModel()
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
class TransactorHttpClient implements AccountClient {
|
||||
private model: ModelDb | undefined
|
||||
private hierarchy: Hierarchy | undefined
|
||||
|
||||
constructor (
|
||||
private readonly token: string,
|
||||
private readonly workspaceId: string,
|
||||
private readonly httpApiWorkerUrl: string
|
||||
) {}
|
||||
|
||||
private url (method: string): string {
|
||||
return `${this.httpApiWorkerUrl}/${method}/${encodeURIComponent(this.workspaceId)}`
|
||||
}
|
||||
|
||||
async loadModel (): Promise<void> {
|
||||
const response = await fetch(this.url('model'), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/octet-stream',
|
||||
Authorization: 'Bearer ' + this.token
|
||||
}
|
||||
})
|
||||
const compressed = await (response as any).bytes()
|
||||
const { model, hierarchy } = await decodeModel(compressed)
|
||||
this.model = model
|
||||
this.hierarchy = hierarchy
|
||||
}
|
||||
|
||||
notify (...tx: Tx[]): void {
|
||||
// does nothing
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy {
|
||||
if (this.hierarchy === undefined) {
|
||||
throw new Error('Hierarchy is not loaded, please use loadModel=true when initializing client')
|
||||
}
|
||||
return this.hierarchy
|
||||
}
|
||||
|
||||
getModel (): ModelDb {
|
||||
if (this.model === undefined) {
|
||||
throw new Error('Model is not loaded, please use loadModel=true when initializing client')
|
||||
}
|
||||
return this.model
|
||||
}
|
||||
|
||||
async findOne<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<WithLookup<T> | undefined> {
|
||||
return (await this.findAll(_class, query, options)).shift()
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
// does nothing
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
const response = await fetch(this.url('find-all'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + this.token
|
||||
},
|
||||
body: JSON.stringify({ _class, query, options })
|
||||
})
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async tx (tx: Tx): Promise<TxResult> {
|
||||
const response = await fetch(this.url('tx'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + this.token
|
||||
},
|
||||
body: JSON.stringify(tx)
|
||||
})
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async searchFulltext (query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
|
||||
// TODO
|
||||
const result: SearchResult = {
|
||||
docs: [],
|
||||
total: 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async getAccount (): Promise<Account> {
|
||||
const response = await fetch(this.url('account'), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: 'Bearer ' + this.token
|
||||
}
|
||||
})
|
||||
return await response.json()
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export * from './http'
|
||||
export * from './account'
|
||||
export * from './rpc'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
@@ -1,169 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
type Account,
|
||||
type AccountClient,
|
||||
type Class,
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
type FindOptions,
|
||||
type FindResult,
|
||||
type Ref,
|
||||
type SearchOptions,
|
||||
type SearchQuery,
|
||||
type SearchResult,
|
||||
type Storage,
|
||||
type Tx,
|
||||
type TxResult,
|
||||
type WithLookup,
|
||||
type Hierarchy,
|
||||
type ModelDb
|
||||
} from '@hcengineering/core'
|
||||
import { decodeModel } from './utils'
|
||||
import { type ConnectOptions } from './types'
|
||||
import { getWorkspaceLogin } from './account'
|
||||
|
||||
export interface TransactorService {
|
||||
openRpc: (token: string, workspaceId: string) => Promise<TransactorRawApi>
|
||||
}
|
||||
|
||||
export interface TransactorRawApi extends Storage {
|
||||
getModel: () => Promise<Buffer>
|
||||
getAccount: () => Promise<Account>
|
||||
}
|
||||
|
||||
export async function createRpcClient (
|
||||
transactorService: TransactorService,
|
||||
options: ConnectOptions
|
||||
): Promise<AccountClient> {
|
||||
let token = options.workspaceToken
|
||||
if (token === undefined) {
|
||||
if (options.authOptions === undefined) {
|
||||
throw new Error('Either workspaceToken or authOptions must be provided')
|
||||
}
|
||||
if (options.configUrl === '' && options.serverConfig === undefined) {
|
||||
throw new Error('Either configUrl or serverConfig must be provided')
|
||||
}
|
||||
const ws = await getWorkspaceLogin(options.configUrl ?? '', options.authOptions, options.serverConfig)
|
||||
token = ws.token
|
||||
}
|
||||
const client = new TransactorRpcClient(token, options.workspaceId ?? '', transactorService)
|
||||
if (options.loadModel === true) {
|
||||
await client.loadModel()
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
class TransactorRpcClient implements AccountClient {
|
||||
private disposed = false
|
||||
private model: ModelDb | undefined
|
||||
private hierarchy: Hierarchy | undefined
|
||||
private transactorRpcStub: TransactorRawApi | undefined
|
||||
private account: Account | undefined
|
||||
|
||||
constructor (
|
||||
private readonly token: string,
|
||||
private readonly workspaceId: string,
|
||||
private readonly transactorService: TransactorService
|
||||
) {}
|
||||
|
||||
private async transactorStub (): Promise<TransactorRawApi> {
|
||||
if (this.transactorRpcStub === undefined) {
|
||||
this.transactorRpcStub = await this.transactorService.openRpc(this.token, this.workspaceId)
|
||||
}
|
||||
return this.transactorRpcStub
|
||||
}
|
||||
|
||||
async loadModel (): Promise<void> {
|
||||
const stub = await this.transactorStub()
|
||||
const compressed = await stub.getModel()
|
||||
const { model, hierarchy } = await decodeModel(compressed)
|
||||
this.model = model
|
||||
this.hierarchy = hierarchy
|
||||
}
|
||||
|
||||
notify (...tx: Tx[]): void {
|
||||
// does nothing
|
||||
}
|
||||
|
||||
getHierarchy (): Hierarchy {
|
||||
if (this.hierarchy === undefined) {
|
||||
throw new Error('Hierarchy is not loaded, please use loadModel=true when initializing client')
|
||||
}
|
||||
return this.hierarchy
|
||||
}
|
||||
|
||||
getModel (): ModelDb {
|
||||
if (this.model === undefined) {
|
||||
throw new Error('Model is not loaded, please use loadModel=true when initializing client')
|
||||
}
|
||||
return this.model
|
||||
}
|
||||
|
||||
async findOne<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<WithLookup<T> | undefined> {
|
||||
return (await this.findAll(_class, query, options)).shift()
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
this.dispose()
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
const stub = await this.transactorStub()
|
||||
return await stub.findAll(_class, query, options)
|
||||
}
|
||||
|
||||
async tx (tx: Tx): Promise<TxResult> {
|
||||
const stub = await this.transactorStub()
|
||||
return await stub.tx(tx)
|
||||
}
|
||||
|
||||
async searchFulltext (query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
|
||||
// TODO
|
||||
const result: SearchResult = {
|
||||
docs: [],
|
||||
total: 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async getAccount (): Promise<Account> {
|
||||
if (this.account === undefined) {
|
||||
const stub = await this.transactorStub()
|
||||
this.account = await stub.getAccount()
|
||||
}
|
||||
return this.account
|
||||
}
|
||||
|
||||
private dispose (): void {
|
||||
if (!this.disposed && this.transactorRpcStub !== undefined && Symbol.dispose in this.transactorRpcStub) {
|
||||
this.disposed = true
|
||||
;(this.transactorRpcStub as any)[Symbol.dispose]()
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.dispose] (): void {
|
||||
this.dispose()
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type AuthOptions, type ServerConfig } from './account'
|
||||
|
||||
export interface ConnectOptions {
|
||||
workspaceToken?: string
|
||||
|
||||
workspaceId?: string
|
||||
|
||||
loadModel?: boolean
|
||||
|
||||
authOptions: AuthOptions
|
||||
|
||||
configUrl?: string
|
||||
|
||||
serverConfig?: ServerConfig
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { gunzip } from 'zlib'
|
||||
import { promisify } from 'util'
|
||||
import {
|
||||
Hierarchy,
|
||||
ModelDb,
|
||||
type MeasureContext,
|
||||
type MeasureLogger,
|
||||
type ParamsType,
|
||||
type Tx
|
||||
} from '@hcengineering/core'
|
||||
|
||||
export async function unpackModel (compressed: Buffer | Uint8Array): Promise<Tx[]> {
|
||||
const ungzipAsync = promisify(gunzip)
|
||||
const buffer = await ungzipAsync(new Uint8Array(compressed))
|
||||
const decoder = new TextDecoder()
|
||||
const jsonString = decoder.decode(buffer)
|
||||
const model = JSON.parse(jsonString) as Tx[]
|
||||
return model
|
||||
}
|
||||
|
||||
export async function decodeModel (compressed: Buffer | Uint8Array): Promise<{ model: ModelDb, hierarchy: Hierarchy }> {
|
||||
const txes = await unpackModel(compressed)
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) {
|
||||
hierarchy.tx(tx)
|
||||
}
|
||||
const model = new ModelDb(hierarchy)
|
||||
const ctx = createDummyMeasureContext()
|
||||
model.addTxes(ctx, txes, false)
|
||||
return { model, hierarchy }
|
||||
}
|
||||
|
||||
function createConsoleLogger (): MeasureLogger {
|
||||
return {
|
||||
info: (message: string, obj?: Record<string, any>) => {
|
||||
console.info(message, obj)
|
||||
},
|
||||
error: (message: string, obj?: Record<string, any>) => {
|
||||
console.error(message, obj)
|
||||
},
|
||||
warn: (message: string, obj?: Record<string, any>) => {
|
||||
console.warn(message, obj)
|
||||
},
|
||||
logOperation: (operation: string, time: number, params: ParamsType) => {
|
||||
console.info(operation, { time, ...params })
|
||||
},
|
||||
close: async () => {}
|
||||
}
|
||||
}
|
||||
|
||||
function createDummyMeasureContext (): MeasureContext {
|
||||
const ctx: MeasureContext = {
|
||||
id: '',
|
||||
contextData: {},
|
||||
newChild: (name, params, fullParams, logger) => {
|
||||
return ctx
|
||||
},
|
||||
with: <T>(name: any, params: any, op: any, fullParams: any) => {
|
||||
return Promise.resolve() as Promise<T>
|
||||
},
|
||||
withSync: (name, params, op, fullParams) => {
|
||||
return op(ctx)
|
||||
},
|
||||
withLog: <T>(name: any, params: any, op: any, fullParams: any) => {
|
||||
return Promise.resolve() as Promise<T>
|
||||
},
|
||||
logger: createConsoleLogger(),
|
||||
measure: (name, value, override) => {},
|
||||
error: (message, args) => {
|
||||
console.error(message, args)
|
||||
},
|
||||
info: (message, args) => {
|
||||
console.info(message, args)
|
||||
},
|
||||
warn: (message, args) => {
|
||||
console.warn(message, args)
|
||||
},
|
||||
end: () => {}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/node/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "@hcengineering/cloud-transactor-http-api",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"template": "cloud",
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev --port 4040",
|
||||
"dev-local": "wrangler dev --port 4040 --local --upstream-protocol=http",
|
||||
"start": "wrangler dev --port 4040",
|
||||
"cf-typegen": "wrangler types",
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"test": "jest --passWithNoTests --silent --forceExit",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent --forceExit",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241022.0",
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3",
|
||||
"wrangler": "^3.97.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"itty-router": "^5.0.18"
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Account, type Storage } from '@hcengineering/core'
|
||||
import { WorkerEntrypoint } from 'cloudflare:workers'
|
||||
import { type IRequest, type IRequestStrict, type RequestHandler, Router, cors, error, html, json } from 'itty-router'
|
||||
|
||||
const { preflight, corsify } = cors({
|
||||
maxAge: 86400
|
||||
})
|
||||
|
||||
const router = Router<IRequestStrict, [Env, ExecutionContext], Response>({
|
||||
before: [preflight],
|
||||
finally: [corsify]
|
||||
})
|
||||
|
||||
type WorkspaceRequest = {
|
||||
workspaceId: string
|
||||
token: string
|
||||
} & IRequestStrict
|
||||
|
||||
interface TransactorService {
|
||||
openRpc: (token: string, workspaceId: string) => Promise<TransactorRawApi>
|
||||
}
|
||||
|
||||
interface TransactorRawApi extends Storage {
|
||||
getModel: () => Promise<Buffer>
|
||||
getAccount: () => Promise<Account>
|
||||
}
|
||||
|
||||
const withWorkspace: RequestHandler<WorkspaceRequest> = (request: WorkspaceRequest) => {
|
||||
if (request.params.workspaceId === undefined || request.params.workspaceId === '') {
|
||||
return error(400, 'Missing workspace')
|
||||
}
|
||||
const token = request.headers.get('Authorization')
|
||||
if (token === null) {
|
||||
return error(401, 'Missing Authorization header')
|
||||
}
|
||||
request.workspaceId = decodeURIComponent(request.params.workspaceId)
|
||||
request.token = token.split(' ')[1]
|
||||
}
|
||||
|
||||
async function callTransactor (
|
||||
request: WorkspaceRequest,
|
||||
env: Env,
|
||||
method: (transactorRpc: TransactorRawApi) => Promise<Response>
|
||||
): Promise<Response> {
|
||||
const transactorService = env.TRANSACTOR_SERVICE as any as TransactorService
|
||||
const transactorRpc = await transactorService.openRpc(request.token, request.workspaceId)
|
||||
try {
|
||||
return await method(transactorRpc)
|
||||
} finally {
|
||||
if (Symbol.dispose in transactorRpc) {
|
||||
;(transactorRpc as any)[Symbol.dispose]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
router.post('/find-all/:workspaceId', withWorkspace, async (request: WorkspaceRequest, env: Env) => {
|
||||
return await callTransactor(request, env, async (transactorRpc) => {
|
||||
if (request.body === null) {
|
||||
return error(400, 'Missing body')
|
||||
}
|
||||
const { _class, query, options }: any = await request.json()
|
||||
const result = await transactorRpc.findAll(_class, query, options)
|
||||
return json(result)
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/tx/:workspaceId', withWorkspace, async (request: WorkspaceRequest, env: Env) => {
|
||||
return await callTransactor(request, env, async (transactorRpc) => {
|
||||
if (request.body === null) {
|
||||
return error(400, 'Missing body')
|
||||
}
|
||||
const tx: any = await request.json()
|
||||
const result = await transactorRpc.tx(tx)
|
||||
return json(result)
|
||||
})
|
||||
})
|
||||
|
||||
router.get('/model/:workspaceId', withWorkspace, async (request: WorkspaceRequest, env: Env) => {
|
||||
return await callTransactor(request, env, async (transactorRpc) => {
|
||||
const model = await transactorRpc.getModel()
|
||||
return new Response(model, {
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Length': model.length.toString()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
router.get('/account/:workspaceId', withWorkspace, async (request: WorkspaceRequest, env: Env) => {
|
||||
return await callTransactor(request, env, async (transactorRpc) => {
|
||||
const account = await transactorRpc.getAccount()
|
||||
return json(account)
|
||||
})
|
||||
})
|
||||
|
||||
router.all('/', () =>
|
||||
html(
|
||||
`Huly® Transactor API™ <a href="https://huly.io">https://huly.io</a>
|
||||
© 2024 <a href="https://hulylabs.com">Huly Labs</a>`
|
||||
)
|
||||
)
|
||||
|
||||
router.all('*', () => error(404))
|
||||
|
||||
export default class TransactorHttpApiWorker extends WorkerEntrypoint {
|
||||
async fetch (request: IRequest): Promise<Response> {
|
||||
return await router.fetch(request, this.env, this.ctx).catch(error)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo",
|
||||
"types": ["@cloudflare/workers-types", "node", "jest"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Generated by Wrangler by running `wrangler types`
|
||||
|
||||
interface Env {
|
||||
TRANSACTOR_SERVICE: Fetcher;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "cloud-transactor-http-api"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-09-23"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
keep_vars = true
|
||||
|
||||
[[services]]
|
||||
binding = "TRANSACTOR_SERVICE"
|
||||
service = "cloud-transactor"
|
||||
entrypoint = "TransactorRpc"
|
||||
Reference in New Issue
Block a user