Init cockroach adapter (#4)

* Init cockroach adapter
This commit is contained in:
Kristina
2024-12-23 17:26:39 +04:00
committed by GitHub
parent e2de49ec97
commit e884fbec2f
18 changed files with 877 additions and 50 deletions
BIN
View File
Binary file not shown.
+4 -1
View File
@@ -1,6 +1,7 @@
{
"name": "@communication/cockroach",
"version": "0.1.0",
"main": "src/index.ts",
"module": "src/index.ts",
"type": "module",
"devDependencies": {
@@ -8,8 +9,10 @@
},
"dependencies": {
"@communication/types": "workspace:*",
"@communication/sdk-types": "workspace:*",
"pg": "8.12.0",
"postgres": "^3.4.4"
"postgres": "^3.4.4",
"uuid": "^11.0.3"
},
"peerDependencies": {
"typescript": "^5.6.3"
+122
View File
@@ -0,0 +1,122 @@
import type postgres from 'postgres'
import {
type Message,
type FindMessagesParams,
type CardID,
type RichText,
type SocialID,
type MessageID,
type ContextID,
type NotificationContextUpdate,
type FindNotificationContextParams,
type NotificationContext,
type FindNotificationsParams,
type Notification
} from '@communication/types'
import type { DbAdapter } from '@communication/sdk-types'
import { MessagesDb } from './db/message'
import { NotificationsDb } from './db/notification'
import { connect, type PostgresClientReference } from './connection'
export class CockroachAdapter implements DbAdapter {
private readonly message: MessagesDb
private readonly notification: NotificationsDb
constructor(
private readonly db: PostgresClientReference,
private readonly sqlClient: postgres.Sql
) {
this.message = new MessagesDb(this.sqlClient)
this.notification = new NotificationsDb(this.sqlClient)
}
async createMessage(content: RichText, creator: SocialID, created: Date): Promise<MessageID> {
return await this.message.createMessage(content, creator, created)
}
async placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void> {
return await this.message.placeMessage(message, card, workspace)
}
async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void> {
return await this.message.createPatch(message, content, creator, created)
}
async removeMessage(message: MessageID): Promise<void> {
return await this.message.removeMessage(message)
}
async createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void> {
return await this.message.createReaction(message, reaction, creator, created)
}
async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise<void> {
return await this.message.removeReaction(message, reaction, creator)
}
async createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise<void> {
return await this.message.createAttachment(message, card, creator, created)
}
async removeAttachment(message: MessageID, card: CardID): Promise<void> {
return await this.message.removeAttachment(message, card)
}
async findMessages(workspace: string, params: FindMessagesParams): Promise<Message[]> {
return await this.message.find(workspace, params)
}
async createNotification(message: MessageID, context: ContextID): Promise<void> {
return await this.notification.createNotification(message, context)
}
async removeNotification(message: MessageID, context: ContextID): Promise<void> {
return await this.notification.removeNotification(message, context)
}
async createContext(
workspace: string,
card: CardID,
personWorkspace: string,
lastView?: Date,
lastUpdate?: Date
): Promise<ContextID> {
return await this.notification.createContext(workspace, card, personWorkspace, lastView, lastUpdate)
}
async updateContext(context: ContextID, update: NotificationContextUpdate): Promise<void> {
return await this.notification.updateContext(context, update)
}
async removeContext(context: ContextID): Promise<void> {
return await this.notification.removeContext(context)
}
async findContexts(
params: FindNotificationContextParams,
personWorkspaces: string[],
workspace?: string
): Promise<NotificationContext[]> {
return await this.notification.findContexts(params, personWorkspaces, workspace)
}
async findNotifications(
params: FindNotificationsParams,
personWorkspace: string,
workspace?: string
): Promise<Notification[]> {
return await this.notification.findNotifications(params, personWorkspace, workspace)
}
close(): void {
this.db.close()
}
}
export async function createDbAdapter(connectionString: string): Promise<DbAdapter> {
const db = connect(connectionString)
const sqlClient = await db.getClient()
return new CockroachAdapter(db, sqlClient)
}
+104
View File
@@ -0,0 +1,104 @@
//Full copy from @hcengineering/postgres
import postgres from 'postgres'
import { v4 as uuid } from 'uuid'
const connections = new Map<string, PostgresClientReferenceImpl>()
const clientRefs = new Map<string, ClientRef>()
export interface PostgresClientReference {
getClient: () => Promise<postgres.Sql>
close: () => void
}
class PostgresClientReferenceImpl {
count: number
client: postgres.Sql | Promise<postgres.Sql>
constructor(
client: postgres.Sql | Promise<postgres.Sql>,
readonly onclose: () => void
) {
this.count = 0
this.client = client
}
async getClient(): Promise<postgres.Sql> {
if (this.client instanceof Promise) {
this.client = await this.client
}
return this.client
}
close(force: boolean = false): void {
this.count--
if (this.count === 0 || force) {
if (force) {
this.count = 0
}
void (async () => {
this.onclose()
const cl = await this.client
await cl.end()
console.log('Closed postgres connection')
})()
}
}
addRef(): void {
this.count++
console.log('Add postgres connection', this.count)
}
}
export class ClientRef implements PostgresClientReference {
id = uuid()
constructor(readonly client: PostgresClientReferenceImpl) {
clientRefs.set(this.id, this)
}
closed = false
async getClient(): Promise<postgres.Sql> {
if (!this.closed) {
return await this.client.getClient()
} else {
throw new Error('DB client-query is already closed')
}
}
close(): void {
// Do not allow double close of connection client-query
if (!this.closed) {
clientRefs.delete(this.id)
this.closed = true
this.client.close()
}
}
}
export function connect(connectionString: string, database?: string): PostgresClientReference {
const extraOptions = JSON.parse(process.env.POSTGRES_OPTIONS ?? '{}')
const key = `${connectionString}${extraOptions}`
let existing = connections.get(key)
if (existing === undefined) {
const sql = postgres(connectionString, {
connection: {
application_name: 'communication'
},
database,
max: 10,
transform: {
undefined: null
},
...extraOptions
})
existing = new PostgresClientReferenceImpl(sql, () => {
connections.delete(key)
})
connections.set(key, existing)
}
// Add reference and return once closable
existing.addRef()
return new ClientRef(existing)
}
+41
View File
@@ -0,0 +1,41 @@
import type postgres from 'postgres'
export class BaseDb {
constructor(
readonly client: postgres.Sql
) {}
async insert(table: string, data: Record<string, any>): Promise<void> {
const keys = Object.keys(data)
const values = Object.values(data)
const sql = `
INSERT INTO ${table} (${keys.map((k) => `"${k}"`).join(', ')})
VALUES (${keys.map((_, idx) => `$${idx + 1}`).join(', ')});
`
await this.client.unsafe(sql, values)
}
async insertWithReturn(table: string, data: Record<string, any>, returnField : string): Promise<any> {
const keys = Object.keys(data)
const values = Object.values(data)
const sql = `
INSERT INTO ${table} (${keys.map((k) => `"${k}"`).join(', ')})
VALUES (${keys.map((_, idx) => `$${idx + 1}`).join(', ')})
RETURNING ${returnField};`
const result =await this.client.unsafe(sql, values)
return result[0][returnField]
}
async remove(table: string, where: Record<string, any>): Promise<void> {
const keys = Object.keys(where)
const values = Object.values(where)
const sql = `
DELETE
FROM ${table}
WHERE ${keys.map((k, idx) => `"${k}" = $${idx + 1}`).join(' AND ')};`
await this.client.unsafe(sql, values)
}
}
+228
View File
@@ -0,0 +1,228 @@
import {
type Message,
type MessageID,
type CardID,
type FindMessagesParams,
SortOrder,
type SocialID,
type RichText,
Direction, type Reaction, type Attachment
} from '@communication/types'
import {BaseDb} from './base.ts'
import {
TableName,
type MessageDb,
type MessagePlaceDb,
type AttachmentDb,
type ReactionDb,
type PatchDb
} from './types.ts'
export class MessagesDb extends BaseDb {
//Message
async createMessage(content: RichText, creator: SocialID, created: Date): Promise<MessageID> {
const dbData: MessageDb = {
content: content,
creator: creator,
created: created,
}
const id = await this.insertWithReturn(TableName.Message, dbData, 'id')
return id as MessageID
}
async removeMessage(message: MessageID): Promise<void> {
await this.remove(TableName.Message, {id: message})
}
async placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void> {
const dbData: MessagePlaceDb = {
workspace_id: workspace,
card_id: card,
message_id: message
}
await this.insert(TableName.MessagePlace, dbData)
}
async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void> {
const dbData: PatchDb = {
message_id: message,
content: content,
creator: creator,
created: created
}
await this.insert(TableName.Patch, dbData)
}
//Attachment
async createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise<void> {
const dbData: AttachmentDb = {
message_id: message,
card_id: card,
creator: creator,
created: created
}
await this.insert(TableName.Attachment, dbData)
}
async removeAttachment(message: MessageID, card: CardID): Promise<void> {
await this.remove(TableName.Attachment, {
message_id: message,
card_id: card
})
}
//Reaction
async createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void> {
const dbData: ReactionDb = {
message_id: message,
reaction: reaction,
creator: creator,
created: created
}
await this.insert(TableName.Reaction, dbData)
}
async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise<void> {
await this.remove(TableName.Reaction, {
message_id: message,
reaction: reaction,
creator: creator
})
}
//Find messages
async find(workspace: string, params: FindMessagesParams): Promise<Message[]> {
//TODO: experiment with select to improve performance
const select = `SELECT m.id,
m.content,
m.creator,
m.created,
${this.subSelectPatches()},
${this.subSelectAttachments()},
${this.subSelectReactions()}
FROM ${TableName.Message} m
INNER JOIN ${TableName.MessagePlace} mp ON m.id = mp.message_id`
const {where, values} = this.buildMessageWhere(workspace, params)
const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
const limit = params.limit ? ` LIMIT ${params.limit}` : ''
const sql = [select, where, orderBy, limit].join(' ')
const result = await this.client.unsafe(sql, values)
return result.map(it => this.toMessage(it)) as Message[]
}
buildMessageWhere(workspace: string, params: FindMessagesParams): { where: string, values: any[] } {
const where: string[] = ['mp.workspace_id = $1']
const values: any[] = [workspace]
let index = 2
for (const key of Object.keys(params)) {
const value = (params as any)[key]
switch (key) {
case 'id': {
where.push(`m.id = $${index++}`)
values.push(value)
break
}
case 'card': {
where.push(`mp.card_id = $${index++}`)
values.push(value)
break
}
case 'from': {
const exclude = params.excluded ?? false
const direction = params.direction ?? Direction.Forward
const getOperator = () => {
if (exclude) {
return direction === Direction.Forward ? '>' : '<'
} else {
return direction === Direction.Forward ? '>=' : '<='
}
}
where.push(`m.created ${getOperator()} $${index++}`)
values.push(value)
break
}
}
}
return {where: `WHERE ${where.join(' AND ')}`, values}
}
subSelectPatches(): string {
return `array(
SELECT jsonb_build_object(
'content', p.content,
'creator', p.creator,
'created', p.created
)
FROM ${TableName.Patch} p
WHERE p.message_id = m.id
) AS patches`
}
subSelectAttachments(): string {
return `array(
SELECT jsonb_build_object(
'card_id', a.card_id,
'message_id', a.message_id,
'creator', a.creator,
'created', a.created
)
FROM ${TableName.Attachment} a
WHERE a.message_id = m.id
) AS attachments`
}
subSelectReactions(): string {
return `array(
SELECT jsonb_build_object(
'message_id', r.message_id,
'reaction', r.reaction,
'creator', r.creator,
'created', r.created
)
FROM ${TableName.Reaction} r
WHERE r.message_id = m.id
) AS reactions`
}
toMessage(row: any): Message {
const lastPatch = row.patches?.[0]
return {
id: row.id,
content: lastPatch?.content ?? row.content,
creator: row.creator,
created: new Date(row.created),
edited: new Date(lastPatch?.created ?? row.created),
reactions: (row.reactions ?? []).map(this.toReaction),
attachments: (row.attachments ?? []).map(this.toAttachment)
}
}
toReaction(row: any): Reaction {
return {
message: row.message_id,
reaction: row.reaction,
creator: row.creator,
created: new Date(row.created)
}
}
toAttachment(row: any): Attachment {
return {
message: row.message_id,
card: row.card_id,
creator: row.creator,
created: new Date(row.created)
}
}
}
+239
View File
@@ -0,0 +1,239 @@
import {
type MessageID,
type ContextID,
type CardID,
type NotificationContext,
type FindNotificationContextParams, SortOrder,
type FindNotificationsParams, type Notification,
type NotificationContextUpdate
} from '@communication/types'
import {BaseDb} from './base.ts'
import {TableName, type ContextDb, type NotificationDb} from './types.ts'
export class NotificationsDb extends BaseDb {
async createNotification(message: MessageID, context: ContextID): Promise<void> {
const dbData: NotificationDb = {
message_id: message,
context
}
await this.insert(TableName.Notification, dbData)
}
async removeNotification(message: MessageID, context: ContextID): Promise<void> {
await this.remove(TableName.Notification, {
message_id: message,
context
})
}
async createContext(workspace: string, card: CardID, personWorkspace: string, lastView?: Date, lastUpdate?: Date): Promise<ContextID> {
const dbData: ContextDb = {
workspace_id: workspace,
card_id: card,
person_workspace: personWorkspace,
last_view: lastView,
last_update: lastUpdate
}
return await this.insertWithReturn(TableName.NotificationContext, dbData, 'id') as ContextID
}
async removeContext(context: ContextID): Promise<void> {
await this.remove(TableName.NotificationContext, {
id: context
})
}
async updateContext(context: ContextID, update: NotificationContextUpdate): Promise<void> {
const dbData: Partial<ContextDb> = {}
if (update.archivedFrom != null) {
dbData.archived_from = update.archivedFrom
}
if (update.lastView != null) {
dbData.last_view = update.lastView
}
if (update.lastUpdate != null) {
dbData.last_update = update.lastUpdate
}
if (Object.keys(dbData).length === 0) {
return
}
const keys = Object.keys(dbData)
const values = Object.values(dbData)
const sql = `UPDATE ${TableName.NotificationContext}
SET ${keys.map((k, idx) => `"${k}" = $${idx + 1}`).join(', ')}
WHERE id =$${keys.length + 1}`
await this.client.unsafe(sql, [values, context])
}
async findContexts( params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): Promise<NotificationContext[]> {
const select = `
SELECT nc.id, nc.card_id, nc.archived_from, nc.last_view, nc.last_update
FROM ${TableName.NotificationContext} nc`;
const {where, values} = this.buildContextWhere(params, personWorkspaces, workspace)
// const orderSql = `ORDER BY nc.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}`
const limit = params.limit ? ` LIMIT ${params.limit}` : ''
const sql = [select, where, limit].join(' ')
const result = await this.client.unsafe(sql, values);
return result.map(this.toNotificationContext);
}
async findNotifications(params: FindNotificationsParams, personWorkspace: string, workspace?: string): Promise<Notification[]> {
//TODO: experiment with select to improve performance, should join with attachments and reactions?
const select = `
SELECT n.message_id,
n.context,
m.content AS message_content,
m.creator AS message_creator,
m.created AS message_created,
nc.card_id,
nc.archived_from,
nc.last_view,
nc.last_update,
(SELECT json_agg(
jsonb_build_object(
'id', p.id,
'content', p.content,
'creator', p.creator,
'created', p.created
)
)
FROM ${TableName.Patch} p
WHERE p.message_id = m.id) AS patches
FROM ${TableName.Notification} n
JOIN ${TableName.NotificationContext} nc ON n.context = nc.id
JOIN ${TableName.Message} m ON n.message_id = m.id
`;
const {where, values} = this.buildNotificationWhere(params, personWorkspace, workspace)
const orderBy = params.sort ? `ORDER BY m.created ${params.sort === SortOrder.Asc ? 'ASC' : 'DESC'}` : ''
const limit = params.limit ? ` LIMIT ${params.limit}` : ''
const sql = [select, where, orderBy, limit].join(' ')
const result = await this.client.unsafe(sql, values);
return result.map(this.toNotification);
}
buildContextWhere(params: FindNotificationContextParams, personWorkspaces: string[], workspace?: string,): {
where: string,
values: any[]
} {
const where: string[] = []
const values: any[] = []
let index = 1
if(workspace != null) {
where.push(`nc.workspace_id = $${index++}`)
values.push(workspace)
}
if(personWorkspaces.length > 0) {
where.push(`nc.person_workspace IN (${personWorkspaces.map((it) => `$${index++}`).join(', ')})`)
values.push(...personWorkspaces)
}
for (const key of Object.keys(params)) {
const value = (params as any)[key]
switch (key) {
case 'card': {
where.push(`nc.card_id = $${index++}`)
values.push(value)
break
}
}
}
return {where: `WHERE ${where.join(' AND ')}`, values}
}
buildNotificationWhere(params: FindNotificationsParams, personWorkspace: string, workspace?: string): {
where: string,
values: any[]
} {
const where: string[] = ['nc.person_workspace = $1']
const values: any[] = [personWorkspace]
let index = 2
if(workspace != null) {
where.push(`nc.workspace_id = $${index++}`)
values.push(workspace)
}
for (const key of Object.keys(params)) {
const value = (params as any)[key]
switch (key) {
case 'context': {
where.push(`n.context = $${index++}`)
values.push(value)
break
}
case 'card': {
where.push(`nc.card_id = $${index++}`)
values.push(value)
break
}
case 'read': {
if (value === true) {
where.push(`nc.last_view IS NOT NULL AND nc.last_view >= m.created`)
} else if (value === false) {
where.push(`(nc.last_view IS NULL OR nc.last_view > m.created)`)
}
break
}
case 'archived': {
if (value === true) {
where.push(`nc.archived_from IS NOT NULL AND nc.archived_from >= m.created`)
} else if (value === false) {
where.push(`(nc.archived_from IS NULL OR nc.archived_from > m.created)`)
}
break
}
}
}
return {where: `WHERE ${where.join(' AND ')}`, values}
}
toNotificationContext(row: any): NotificationContext {
return {
id: row.id,
card: row.card_id,
workspace: row.workspace_id,
personWorkspace: row.person_workspace,
archivedFrom: row.archived_from ? new Date(row.archived_from) : undefined,
lastView: row.last_view ? new Date(row.last_view) : undefined,
lastUpdate: row.last_update ? new Date(row.last_update) : undefined
}
}
toNotification(row: any): Notification {
const lastPatch = row.patches?.[0]
const lastView = row.last_view ? new Date(row.last_view) : undefined
const archivedFrom = row.archived_from ? new Date(row.archived_from) : undefined
const created = new Date(row.message_created)
return {
message: {
id: row.id,
content: lastPatch?.content ?? row.message_content,
creator: row.message_creator,
created,
edited: new Date(lastPatch?.created ?? row.message_created),
reactions: row.reactions ?? [],
attachments: row.attachments ?? []
},
context: row.context,
read: lastView != null && lastView >= created,
archived: archivedFrom != null && archivedFrom >= created
}
}
}
+59
View File
@@ -0,0 +1,59 @@
import type {CardID, ContextID, MessageID, RichText, SocialID } from "@communication/types"
export enum TableName {
Message = 'message',
Patch = 'patch',
MessagePlace = 'message_place',
Attachment = 'attachment',
Reaction = 'reaction',
Notification = 'notification',
NotificationContext = 'notification_context'
}
export interface MessageDb {
content: RichText,
creator: SocialID,
created: Date,
}
export interface PatchDb {
message_id: MessageID,
content: RichText,
creator: SocialID,
created: Date,
}
export interface MessagePlaceDb {
workspace_id: string,
card_id: CardID,
message_id: MessageID
}
export interface ReactionDb {
message_id: MessageID,
reaction: string,
creator: SocialID
created: Date
}
export interface AttachmentDb {
message_id: MessageID,
card_id: CardID,
creator: SocialID
created: Date
}
export interface NotificationDb {
message_id: MessageID,
context: ContextID
}
export interface ContextDb {
workspace_id: string
card_id: CardID
person_workspace: string
archived_from?: Date
last_view?: Date
last_update?: Date
}
+1
View File
@@ -0,0 +1 @@
export * from './adapter.ts'
@@ -1,19 +0,0 @@
CREATE TABLE IF NOT EXISTS message
(
id INT8 NOT NULL DEFAULT unique_rowid(),
content TEXT,
version INTEGER NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (id, version)
);
CREATE TABLE IF NOT EXISTS message_place
(
workspace_id UUID NOT NULL,
card_id UUID NOT NULL,
message_id INT8 NOT NULL,
PRIMARY KEY (workspace_id, card_id, message_id)
);
@@ -1,11 +0,0 @@
CREATE TABLE IF NOT EXISTS reaction
(
message_id INT8 NOT NULL,
reaction INTEGER NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (message_id, creator, reaction)
);
CREATE INDEX IF NOT EXISTS reaction_message_idx ON reaction (message_id);
@@ -1,7 +0,0 @@
CREATE TABLE IF NOT EXISTS notification
(
social_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
PRIMARY KEY (social_id, message_id)
);
@@ -1,12 +0,0 @@
CREATE TABLE IF NOT EXISTS notification_context
(
workspace_id UUID NOT NULL,
card_id UUID NOT NULL,
huly_id VARCHAR(255) NOT NULL, /* Or maybe account id or something else */
archived_from TIMESTAMPTZ,
last_view TIMESTAMPTZ,
last_update TIMESTAMPTZ,
PRIMARY KEY (workspace_id, card_id, huly_id)
);
View File
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@communication/sdk-types",
"version": "0.1.0",
"main": "src/index.ts",
"module": "src/index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "^1.1.14"
},
"dependencies": {
"@communication/types": "workspace:*"
},
"peerDependencies": {
"typescript": "^5.6.3"
}
}
+54
View File
@@ -0,0 +1,54 @@
import type {
CardID,
ContextID,
FindMessagesParams,
FindNotificationContextParams,
FindNotificationsParams,
Message,
MessageID,
NotificationContext,
NotificationContextUpdate,
RichText,
SocialID,
Notification
} from '@communication/types'
export interface DbAdapter {
createMessage(content: RichText, creator: SocialID, created: Date): Promise<MessageID>
removeMessage(id: MessageID): Promise<void>
placeMessage(message: MessageID, card: CardID, workspace: string): Promise<void>
createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise<void>
createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise<void>
removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise<void>
createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise<void>
removeAttachment(message: MessageID, card: CardID): Promise<void>
findMessages(workspace: string, query: FindMessagesParams): Promise<Message[]>
createNotification(message: MessageID, context: ContextID): Promise<void>
removeNotification(message: MessageID, context: ContextID): Promise<void>
createContext(
personWorkspace: string,
workspace: string,
card: CardID,
lastView?: Date,
lastUpdate?: Date
): Promise<ContextID>
updateContext(context: ContextID, update: NotificationContextUpdate): Promise<void>
removeContext(context: ContextID): Promise<void>
findContexts(
params: FindNotificationContextParams,
personWorkspaces: string[],
workspace?: string
): Promise<NotificationContext[]>
findNotifications(
params: FindNotificationsParams,
personWorkspace: string,
workspace?: string
): Promise<Notification[]>
close(): void
}
+1
View File
@@ -0,0 +1 @@
export * from './db'
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}