move workers to another repo (#7560)

Signed-off-by: Nikolay Chunosov <Chunosov.N@gmail.com>
This commit is contained in:
Chunosov 2024-12-26 17:39:41 +07:00 committed by GitHub
parent 8c4c4ad9b3
commit 2d64f89584
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 0 additions and 1282 deletions

View File

@ -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

View File

@ -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
}
]
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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"]
}
}

View File

@ -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;
}

View File

@ -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"

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/node/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,5 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "node"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -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"
}
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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'

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -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"
}
}

View File

@ -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&reg; Transactor API&trade; <a href="https://huly.io">https://huly.io</a>
&copy; 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)
}
}

View File

@ -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"]
}
}

View File

@ -1,5 +0,0 @@
// Generated by Wrangler by running `wrangler types`
interface Env {
TRANSACTOR_SERVICE: Fetcher;
}

View File

@ -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"