diff --git a/bun.lockb b/bun.lockb index 22de1295d3..5db3fe48a9 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/cockroach/package.json b/packages/cockroach/package.json index ac6244f46b..0cdf1bb26c 100644 --- a/packages/cockroach/package.json +++ b/packages/cockroach/package.json @@ -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" diff --git a/packages/cockroach/src/adapter.ts b/packages/cockroach/src/adapter.ts new file mode 100644 index 0000000000..22185295da --- /dev/null +++ b/packages/cockroach/src/adapter.ts @@ -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 { + return await this.message.createMessage(content, creator, created) + } + + async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { + return await this.message.placeMessage(message, card, workspace) + } + + async createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise { + return await this.message.createPatch(message, content, creator, created) + } + + async removeMessage(message: MessageID): Promise { + return await this.message.removeMessage(message) + } + + async createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise { + return await this.message.createReaction(message, reaction, creator, created) + } + + async removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise { + return await this.message.removeReaction(message, reaction, creator) + } + + async createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise { + return await this.message.createAttachment(message, card, creator, created) + } + + async removeAttachment(message: MessageID, card: CardID): Promise { + return await this.message.removeAttachment(message, card) + } + + async findMessages(workspace: string, params: FindMessagesParams): Promise { + return await this.message.find(workspace, params) + } + + async createNotification(message: MessageID, context: ContextID): Promise { + return await this.notification.createNotification(message, context) + } + + async removeNotification(message: MessageID, context: ContextID): Promise { + return await this.notification.removeNotification(message, context) + } + + async createContext( + workspace: string, + card: CardID, + personWorkspace: string, + lastView?: Date, + lastUpdate?: Date + ): Promise { + return await this.notification.createContext(workspace, card, personWorkspace, lastView, lastUpdate) + } + + async updateContext(context: ContextID, update: NotificationContextUpdate): Promise { + return await this.notification.updateContext(context, update) + } + + async removeContext(context: ContextID): Promise { + return await this.notification.removeContext(context) + } + + async findContexts( + params: FindNotificationContextParams, + personWorkspaces: string[], + workspace?: string + ): Promise { + return await this.notification.findContexts(params, personWorkspaces, workspace) + } + + async findNotifications( + params: FindNotificationsParams, + personWorkspace: string, + workspace?: string + ): Promise { + return await this.notification.findNotifications(params, personWorkspace, workspace) + } + + close(): void { + this.db.close() + } +} + +export async function createDbAdapter(connectionString: string): Promise { + const db = connect(connectionString) + const sqlClient = await db.getClient() + + return new CockroachAdapter(db, sqlClient) +} diff --git a/packages/cockroach/src/connection.ts b/packages/cockroach/src/connection.ts new file mode 100644 index 0000000000..a9aa16e74b --- /dev/null +++ b/packages/cockroach/src/connection.ts @@ -0,0 +1,104 @@ +//Full copy from @hcengineering/postgres +import postgres from 'postgres' +import { v4 as uuid } from 'uuid' + +const connections = new Map() +const clientRefs = new Map() + +export interface PostgresClientReference { + getClient: () => Promise + close: () => void +} + +class PostgresClientReferenceImpl { + count: number + client: postgres.Sql | Promise + + constructor( + client: postgres.Sql | Promise, + readonly onclose: () => void + ) { + this.count = 0 + this.client = client + } + + async getClient(): Promise { + 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 { + 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) +} diff --git a/packages/cockroach/src/db/base.ts b/packages/cockroach/src/db/base.ts new file mode 100644 index 0000000000..671ffc80dc --- /dev/null +++ b/packages/cockroach/src/db/base.ts @@ -0,0 +1,41 @@ +import type postgres from 'postgres' + +export class BaseDb { + constructor( + readonly client: postgres.Sql + ) {} + + async insert(table: string, data: Record): Promise { + 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, returnField : string): Promise { + 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): Promise { + 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) + } +} diff --git a/packages/cockroach/src/db/message.ts b/packages/cockroach/src/db/message.ts new file mode 100644 index 0000000000..2722f68bef --- /dev/null +++ b/packages/cockroach/src/db/message.ts @@ -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 { + 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 { + await this.remove(TableName.Message, {id: message}) + } + + async placeMessage(message: MessageID, card: CardID, workspace: string): Promise { + 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 { + 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 { + 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 { + await this.remove(TableName.Attachment, { + message_id: message, + card_id: card + }) + } + + //Reaction + async createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise { + 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 { + await this.remove(TableName.Reaction, { + message_id: message, + reaction: reaction, + creator: creator + }) + } + + //Find messages + async find(workspace: string, params: FindMessagesParams): Promise { + //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) + } + } +} + diff --git a/packages/cockroach/src/db/notification.ts b/packages/cockroach/src/db/notification.ts new file mode 100644 index 0000000000..dfa9528d12 --- /dev/null +++ b/packages/cockroach/src/db/notification.ts @@ -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 { + const dbData: NotificationDb = { + message_id: message, + context + } + await this.insert(TableName.Notification, dbData) + } + + async removeNotification(message: MessageID, context: ContextID): Promise { + await this.remove(TableName.Notification, { + message_id: message, + context + }) + } + + async createContext(workspace: string, card: CardID, personWorkspace: string, lastView?: Date, lastUpdate?: Date): Promise { + 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 { + await this.remove(TableName.NotificationContext, { + id: context + }) + } + + async updateContext(context: ContextID, update: NotificationContextUpdate): Promise { + const dbData: Partial = {} + + 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 { + 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 { + //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 + } + } +} + diff --git a/packages/cockroach/src/db/types.ts b/packages/cockroach/src/db/types.ts new file mode 100644 index 0000000000..9dab08561a --- /dev/null +++ b/packages/cockroach/src/db/types.ts @@ -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 +} \ No newline at end of file diff --git a/packages/cockroach/src/index.ts b/packages/cockroach/src/index.ts new file mode 100644 index 0000000000..03eeab5ffa --- /dev/null +++ b/packages/cockroach/src/index.ts @@ -0,0 +1 @@ +export * from './adapter.ts' diff --git a/packages/postgres/migrations/01_message.sql b/packages/postgres/migrations/01_message.sql deleted file mode 100644 index 8aa19866f9..0000000000 --- a/packages/postgres/migrations/01_message.sql +++ /dev/null @@ -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) -); diff --git a/packages/postgres/migrations/03_reaction.sql b/packages/postgres/migrations/03_reaction.sql deleted file mode 100644 index 5dc21091f7..0000000000 --- a/packages/postgres/migrations/03_reaction.sql +++ /dev/null @@ -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); diff --git a/packages/postgres/migrations/04_notification.sql b/packages/postgres/migrations/04_notification.sql deleted file mode 100644 index 96b53f1a6d..0000000000 --- a/packages/postgres/migrations/04_notification.sql +++ /dev/null @@ -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) -); diff --git a/packages/postgres/migrations/05_notificationContext.sql b/packages/postgres/migrations/05_notificationContext.sql deleted file mode 100644 index f8936f24cc..0000000000 --- a/packages/postgres/migrations/05_notificationContext.sql +++ /dev/null @@ -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) -); diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/sdk-types/package.json b/packages/sdk-types/package.json new file mode 100644 index 0000000000..eccea9085e --- /dev/null +++ b/packages/sdk-types/package.json @@ -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" + } +} diff --git a/packages/sdk-types/src/db.ts b/packages/sdk-types/src/db.ts new file mode 100644 index 0000000000..0e5c9c38d3 --- /dev/null +++ b/packages/sdk-types/src/db.ts @@ -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 + removeMessage(id: MessageID): Promise + + placeMessage(message: MessageID, card: CardID, workspace: string): Promise + createPatch(message: MessageID, content: RichText, creator: SocialID, created: Date): Promise + + createReaction(message: MessageID, reaction: string, creator: SocialID, created: Date): Promise + removeReaction(message: MessageID, reaction: string, creator: SocialID): Promise + + createAttachment(message: MessageID, card: CardID, creator: SocialID, created: Date): Promise + removeAttachment(message: MessageID, card: CardID): Promise + + findMessages(workspace: string, query: FindMessagesParams): Promise + + createNotification(message: MessageID, context: ContextID): Promise + removeNotification(message: MessageID, context: ContextID): Promise + createContext( + personWorkspace: string, + workspace: string, + card: CardID, + lastView?: Date, + lastUpdate?: Date + ): Promise + updateContext(context: ContextID, update: NotificationContextUpdate): Promise + removeContext(context: ContextID): Promise + findContexts( + params: FindNotificationContextParams, + personWorkspaces: string[], + workspace?: string + ): Promise + findNotifications( + params: FindNotificationsParams, + personWorkspace: string, + workspace?: string + ): Promise + + close(): void +} diff --git a/packages/sdk-types/src/index.ts b/packages/sdk-types/src/index.ts new file mode 100644 index 0000000000..1beb455f5e --- /dev/null +++ b/packages/sdk-types/src/index.ts @@ -0,0 +1 @@ +export * from './db' diff --git a/packages/sdk-types/tsconfig.json b/packages/sdk-types/tsconfig.json new file mode 100644 index 0000000000..49e05cea1e --- /dev/null +++ b/packages/sdk-types/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] +}