Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ SCHEMA.md
dist
node_modules
.env
db.sqlite
19 changes: 19 additions & 0 deletions PROBLEMS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# PROBLEMS

## 1) Event middleware

- Encrypted events need to be decrypted before they are resolved

## 2) DB implementations

- Sqlite (android/ios)
- indexeddb (web)
- in-memory

## 3) Data model

- how to model the data in a way that is efficient and easy to query

## 4) encryption

- figure out olm/megolm
97 changes: 97 additions & 0 deletions example/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Config, Effect, Layer, Schema } from 'effect'
import { DevTools } from 'effect/unstable/devtools'
import { MatrixApi, endpoints } from 'mxfx/api'
import { RoomId } from 'mxfx/branded'
import { MatrixConfig } from 'mxfx/config'
import { InMemoryVault } from 'mxfx/vault'

import { MatrixClient } from './client'

const PingCommandEvent = makeEvent('mxfx.ping', {
kind: MatrixEventKind.Timeline,
content: Schema.Struct({
message: Schema.String,
}),
predicate: (event): event is typeof Common.RoomMessageEvent.Encoded => {
return event.type === 'mxfx.ping'
},
})

const program = Effect.gen(function* () {
const matrixApi = yield* MatrixApi.MatrixApi

const client = MatrixClient.make()
// .builder() //
// .registerEvent(PingCommandEvent)
// .build()

const { userId } = yield* client.login()

const y = yield* endpoints.getProfileV3({ userId }).pipe(Effect.andThen(matrixApi.execute))
yield* Effect.log(`Logged in as user ID: ${userId} with profile: ${JSON.stringify(y)}`)

yield* client.on(
MatrixInviteEvent,
Effect.fn('invite')(function* (invite) {
const roomId = yield* RoomId.make(invite.roomId)
yield* endpoints.postRoomsJoinV3({ roomId }).pipe(Effect.andThen(matrixApi.execute))
yield* Effect.log(`Joined room ${roomId} that we were invited to`)
}),
Effect.catch(err => Effect.logError(`Failed to join room ${err}`)),
)

yield* client.on(
PingCommandEvent,
Effect.fn('ping')(function* (event) {
const roomId = yield* RoomId.make(event.roomId)

const responseContent = { msgtype: 'm.text', body: 'Pinging...' }
const { eventId: sendEventId } = yield* endpoints
.putRoomsSendV3({ roomId, content: responseContent, eventType: 'm.room.message' })
.pipe(Effect.andThen(matrixApi.execute))

const pingMs = Date.now() - event.originServerTs
const { eventId: editEventId } = yield* endpoints
.putRoomsSendV3({
roomId,
content: {
msgtype: 'm.text',
body: `* Pong! 🏓 ${pingMs}ms`,
'm.newContent': { msgtype: 'm.text', body: `Pong! 🏓 ${pingMs}ms` },
'm.relatesTo': { relType: 'm.replace', eventId: sendEventId },
},
eventType: 'm.room.message',
})
.pipe(Effect.andThen(matrixApi.execute))

const sendEvent = yield* endpoints.getRoomsEventV3({ roomId, eventId: sendEventId }).pipe(Effect.andThen(matrixApi.execute))
const editEvent = yield* endpoints.getRoomsEventV3({ roomId, eventId: editEventId }).pipe(Effect.andThen(matrixApi.execute))

const fullRTT = editEvent.originServerTs - sendEvent.originServerTs

yield* endpoints
.putRoomsSendV3({
roomId,
content: {
msgtype: 'm.text',
body: `* Pong! 🏓 ${pingMs}ms`,
'm.newContent': { msgtype: 'm.text', body: `Pong! 🏓 ${pingMs}ms | fullRTT: ${fullRTT}ms` },
'm.relatesTo': { relType: 'm.replace', eventId: sendEventId },
},
eventType: 'm.room.message',
})
.pipe(Effect.andThen(matrixApi.execute))
}),
Effect.catch(err => Effect.logError(`Failed to ping room ${err}`)),
)
})

const mxfxLive = MatrixApi.layer.pipe(
Layer.provideMerge(InMemoryVault.layerConfig({ values: { accessToken: Config.string('MATRIX_ACCESS_TOKEN') } })),
Layer.provideMerge(MatrixConfig.layerConfig({ serverName: Config.string('MATRIX_HOME_SERVER') })),

Layer.provideMerge(DevTools.layer()),
// Layer.provideMerge(Logger.layer([Logger.consoleStructured])),
)

NodeRuntime.runMain(program.pipe(Effect.provide(mxfxLive)))
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,35 @@
"type": "module",
"sideEffects": [],
"exports": {
".": "./src/index.ts",
"./api": "./src/api/index.ts",
"./api/endpoints": "./src/api/endpoints/index.ts",
"./api/http-client": "./src/api/http-client/index.ts",
"./api/schema": "./src/api/schema/index.ts",
"./config": "./src/config/index.ts",
"./vault": "./src/vault/index.ts",
"./branded": "./src/branded/index.ts"
"./branded": "./src/branded/index.ts",
"./client": "./src/client/index.ts"
},
"scripts": {
"fmt": "oxfmt",
"fmt:check": "oxfmt --check",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:ui": "vitest --ui"
},
"devDependencies": {
"typescript": "^5.9.3",
"@effect/vitest": "4.0.0-beta.31",
"@types/node": "^24.10.0",
"@effect/platform-node": "4.0.0-beta.36",
"@effect/sql-libsql": "4.0.0-beta.36",
"@effect/vitest": "4.0.0-beta.36",
"oxfmt": "^0.36.0",
"oxlint": "^1.52.0",
"oxlint-tsgolint": "^0.16.0"
"oxlint-tsgolint": "^0.16.0",
"typescript": "^5.9.3",
"vitest": "^3.0.0"
},
"peerDependencies": {
"effect": "4.0.0-beta.31"
"effect": "4.0.0-beta.36"
}
}
Loading