feat: initial deno support
This commit is contained in:
parent
2bc9f898e5
commit
a2cdc73735
50 changed files with 1013 additions and 27 deletions
|
@ -279,7 +279,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/bun/**'],
|
||||
files: ['packages/bun/**', 'packages/deno/**'],
|
||||
rules: {
|
||||
'import/no-unresolved': 'off',
|
||||
'no-restricted-imports': 'off',
|
||||
|
@ -291,7 +291,7 @@ module.exports = {
|
|||
rules: {
|
||||
'import/no-unresolved': 'off',
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
|
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
@ -67,7 +67,7 @@ jobs:
|
|||
- name: 'Build tests'
|
||||
run: pnpm exec vite build -c .config/vite.deno.mts
|
||||
- name: 'Run tests'
|
||||
run: cd dist/tests && deno test
|
||||
run: cd dist/tests && deno test -A --unstable-ffi
|
||||
|
||||
test-web:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
@jsr:registry=https://npm.jsr.io
|
|
@ -10,7 +10,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"prepare": "husky install .config/husky",
|
||||
"postinstall": "node scripts/validate-deps-versions.mjs",
|
||||
"postinstall": "node scripts/validate-deps-versions.mjs && node scripts/fetch-deno-dts.mjs && node scripts/remove-jsr-sourcefiles.mjs",
|
||||
"test": "pnpm run -r test && vitest --config .config/vite.mts run",
|
||||
"test:dev": "vitest --config .config/vite.mts watch",
|
||||
"test:ui": "vitest --config .config/vite.mts --ui",
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import * as os from 'os'
|
||||
|
||||
import { NodePlatform } from './common-internals-node/platform.js'
|
||||
import { normalizeFile } from './utils/normalize-file.js'
|
||||
|
||||
export class BunPlatform extends NodePlatform {
|
||||
declare normalizeFile: typeof normalizeFile
|
||||
|
||||
getDeviceModel(): string {
|
||||
return `Bun/${process.version} (${os.type()} ${os.arch()})`
|
||||
}
|
||||
}
|
||||
|
||||
BunPlatform.prototype.normalizeFile = normalizeFile
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
WorkerMessageHandler,
|
||||
} from '@mtcute/core/worker.js'
|
||||
|
||||
import { NodePlatform } from './common-internals-node/platform.js'
|
||||
import { BunPlatform } from './platform.js'
|
||||
|
||||
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
|
||||
|
||||
|
@ -44,7 +44,7 @@ export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorke
|
|||
|
||||
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
|
||||
constructor(readonly options: TelegramWorkerPortOptions) {
|
||||
setPlatform(new NodePlatform())
|
||||
setPlatform(new BunPlatform())
|
||||
super(options)
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,8 @@ export async function uploadFile(
|
|||
throw new MtArgumentError('Fetch response contains `null` body')
|
||||
}
|
||||
|
||||
file = file.body
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
file = file.body as ReadableStream<Uint8Array>
|
||||
}
|
||||
|
||||
if (!(file instanceof ReadableStream)) {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import type { ReadStream } from 'node:fs'
|
||||
|
||||
import { tdFileId } from '@mtcute/file-id'
|
||||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { AnyToNever } from '../../../types/utils.js'
|
||||
import { FileLocation } from './file-location.js'
|
||||
import { UploadedFile } from './uploaded-file.js'
|
||||
|
||||
|
@ -14,8 +12,9 @@ import { UploadedFile } from './uploaded-file.js'
|
|||
* - `File`, `Blob` (from the Web API)
|
||||
* - `string`, which will be interpreted as file path (**non-browser only!**)
|
||||
* - `URL` (from the Web API, will be `fetch()`-ed; `file://` URLs are not available in browsers)
|
||||
* - `ReadStream` (for Node.js/Bun, from the `fs` module)
|
||||
* - `ReadStream` (for Node.js/Bun, from the `node:fs` module)
|
||||
* - `BunFile` (from `Bun.file()`)
|
||||
* - `Deno.FsFile` (from `Deno.open()` in Deno)
|
||||
* - `ReadableStream` (Web API readable stream)
|
||||
* - `Readable` (Node.js/Bun readable stream)
|
||||
* - `Response` (from `window.fetch`)
|
||||
|
@ -26,10 +25,14 @@ export type UploadFileLike =
|
|||
| File
|
||||
| Blob
|
||||
| string
|
||||
| ReadStream
|
||||
| ReadableStream<Uint8Array>
|
||||
| NodeJS.ReadableStream
|
||||
| Response
|
||||
| AnyToNever<import('node:fs').ReadStream>
|
||||
| AnyToNever<ReadableStream<Uint8Array>>
|
||||
| AnyToNever<NodeJS.ReadableStream>
|
||||
| AnyToNever<Response>
|
||||
| AnyToNever<Deno.FsFile>
|
||||
|
||||
// AnyToNever in the above type ensures we don't make the entire type `any`
|
||||
// if some of the types are not available in the current environment
|
||||
|
||||
/**
|
||||
* Describes types that can be used as an input
|
||||
|
@ -41,6 +44,8 @@ export type UploadFileLike =
|
|||
* - `ReadStream` (for Node.js/Bun, from the `fs` module)
|
||||
* - `ReadableStream` (from the Web API, base readable stream)
|
||||
* - `Readable` (for Node.js/Bun, base readable stream)
|
||||
* - `BunFile` (from `Bun.file()`)
|
||||
* - `Deno.FsFile` (from `Deno.open()` in Deno)
|
||||
* - {@link UploadedFile} returned from {@link TelegramClient.uploadFile}
|
||||
* - `tl.TypeInputFile` and `tl.TypeInputMedia` TL objects
|
||||
* - `string` with a path to a local file prepended with `file:` (non-browser only) (e.g. `file:image.jpg`)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// <reference lib="dom" />
|
||||
import type { Worker as NodeWorker } from 'node:worker_threads'
|
||||
|
||||
import { tl } from '@mtcute/tl'
|
||||
|
|
|
@ -62,10 +62,9 @@ export class SqlitePeersRepository implements IPeersRepository {
|
|||
this._driver._writeLater(this._store, [
|
||||
peer.id,
|
||||
peer.accessHash,
|
||||
// add commas to make it easier to search with LIKE
|
||||
JSON.stringify(peer.usernames),
|
||||
peer.updated,
|
||||
peer.phone,
|
||||
peer.phone ?? null,
|
||||
peer.complete,
|
||||
])
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export type MaybePromise<T> = T | Promise<T>
|
||||
export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
||||
export type PartialOnly<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AnyToNever<T> = any extends T ? never : T
|
||||
|
||||
export type MaybeArray<T> = T | T[]
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ export function reportUnknownError(log: Logger, error: tl.RpcError, method: stri
|
|||
|
||||
fetch(`https://rpc.pwrtelegram.xyz/?code=${error.code}&method=${method}&error=${error.text}`)
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.then((r: any) => {
|
||||
if (r.ok) {
|
||||
log.info('telerpc responded with error info for %s: %s', error.text, r.result)
|
||||
} else {
|
||||
|
|
25
packages/deno/README.md
Normal file
25
packages/deno/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# @mtcute/bun
|
||||
|
||||
📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_deno.html)
|
||||
|
||||
‼️ **Experimental** Deno support package for mtcute. Includes:
|
||||
- SQLite storage (based on [`@db/sqlite`](https://jsr.io/@db/sqlite))
|
||||
- TCP transport (based on Deno-native APIs)
|
||||
- `TelegramClient` implementation using the above
|
||||
- HTML and Markdown parsers
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { TelegramClient } from '@mtcute/deno'
|
||||
|
||||
const tg = new TelegramClient({
|
||||
apiId: 12345,
|
||||
apiHash: 'abcdef',
|
||||
storage: 'my-account'
|
||||
})
|
||||
|
||||
tg.run(async (user) => {
|
||||
console.log(`✨ logged in as ${user.displayName}`)
|
||||
})
|
||||
```
|
1
packages/deno/build.config.cjs
Normal file
1
packages/deno/build.config.cjs
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = () => ({ buildCjs: false })
|
30
packages/deno/package.json
Normal file
30
packages/deno/package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@mtcute/deno",
|
||||
"private": true,
|
||||
"version": "0.11.0",
|
||||
"description": "Meta-package for Deno",
|
||||
"author": "alina sireneva <alina@tei.su>",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package deno"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./utils.js": "./src/utils.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/wasm": "workspace:^",
|
||||
"@mtcute/markdown-parser": "workspace:^",
|
||||
"@mtcute/html-parser": "workspace:^",
|
||||
"@db/sqlite": "npm:@jsr/db__sqlite@0.11.1",
|
||||
"@std/io": "npm:@jsr/std__io@0.223.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^"
|
||||
}
|
||||
}
|
144
packages/deno/src/client.ts
Normal file
144
packages/deno/src/client.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
import { createInterface, Interface as RlInterface } from 'node:readline'
|
||||
import { Readable, Writable } from 'node:stream'
|
||||
|
||||
import { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core'
|
||||
import {
|
||||
BaseTelegramClient as BaseTelegramClientBase,
|
||||
BaseTelegramClientOptions as BaseTelegramClientOptionsBase,
|
||||
TelegramClient as TelegramClientBase,
|
||||
TelegramClientOptions,
|
||||
} from '@mtcute/core/client.js'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
|
||||
import { downloadToFile } from './methods/download-file.js'
|
||||
import { DenoPlatform } from './platform.js'
|
||||
import { SqliteStorage } from './sqlite/index.js'
|
||||
import { DenoCryptoProvider } from './utils/crypto.js'
|
||||
import { TcpTransport } from './utils/tcp.js'
|
||||
|
||||
export type { TelegramClientOptions }
|
||||
|
||||
export interface BaseTelegramClientOptions
|
||||
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
|
||||
/**
|
||||
* Storage to use for this client.
|
||||
*
|
||||
* If a string is passed, it will be used as
|
||||
* a name for an SQLite database file.
|
||||
*
|
||||
* @default `"client.session"`
|
||||
*/
|
||||
storage?: string | ITelegramStorageProvider
|
||||
|
||||
/**
|
||||
* **ADVANCED USE ONLY**
|
||||
*
|
||||
* Whether to not set up the platform.
|
||||
* This is useful if you call `setPlatform` yourself.
|
||||
*/
|
||||
platformless?: boolean
|
||||
}
|
||||
|
||||
export class BaseTelegramClient extends BaseTelegramClientBase {
|
||||
constructor(opts: BaseTelegramClientOptions) {
|
||||
if (!opts.platformless) setPlatform(new DenoPlatform())
|
||||
|
||||
super({
|
||||
crypto: new DenoCryptoProvider(),
|
||||
transport: () => new TcpTransport(),
|
||||
...opts,
|
||||
storage:
|
||||
typeof opts.storage === 'string' ?
|
||||
new SqliteStorage(opts.storage) :
|
||||
opts.storage ?? new SqliteStorage('client.session'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Telegram client for use in Node.js
|
||||
*/
|
||||
export class TelegramClient extends TelegramClientBase {
|
||||
constructor(opts: TelegramClientOptions) {
|
||||
if ('client' in opts) {
|
||||
super(opts)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
super({
|
||||
client: new BaseTelegramClient(opts),
|
||||
})
|
||||
}
|
||||
|
||||
private _rl?: RlInterface
|
||||
|
||||
/**
|
||||
* Tiny wrapper over Node `readline` package
|
||||
* for simpler user input for `.run()` method.
|
||||
*
|
||||
* Associated `readline` interface is closed
|
||||
* after `run()` returns, or with the client.
|
||||
*
|
||||
* @param text Text of the question
|
||||
*/
|
||||
input(text: string): Promise<string> {
|
||||
if (!this._rl) {
|
||||
this._rl = createInterface({
|
||||
// eslint-disable-next-line
|
||||
input: Readable.fromWeb(Deno.stdin.readable as any),
|
||||
output: Writable.fromWeb(Deno.stdout.writable),
|
||||
})
|
||||
}
|
||||
|
||||
return new Promise((res) => this._rl?.question(text, res))
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
this._rl?.close()
|
||||
|
||||
return super.close()
|
||||
}
|
||||
|
||||
start(params: Parameters<TelegramClientBase['start']>[0] = {}): Promise<User> {
|
||||
if (!params.botToken) {
|
||||
if (!params.phone) params.phone = () => this.input('phone > ')
|
||||
if (!params.code) params.code = () => this.input('code > ')
|
||||
|
||||
if (!params.password) {
|
||||
params.password = () => this.input('2fa password > ')
|
||||
}
|
||||
}
|
||||
|
||||
return super.start(params).then((user) => {
|
||||
if (this._rl) {
|
||||
this._rl.close()
|
||||
delete this._rl
|
||||
}
|
||||
|
||||
return user
|
||||
})
|
||||
}
|
||||
|
||||
run(
|
||||
params: Parameters<TelegramClient['start']>[0] | ((user: User) => void | Promise<void>),
|
||||
then?: (user: User) => void | Promise<void>,
|
||||
): void {
|
||||
if (typeof params === 'function') {
|
||||
then = params
|
||||
params = {}
|
||||
}
|
||||
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch((err) => this.emitError(err))
|
||||
}
|
||||
|
||||
downloadToFile(
|
||||
filename: string,
|
||||
location: FileDownloadLocation,
|
||||
params?: FileDownloadParameters | undefined,
|
||||
): Promise<void> {
|
||||
return downloadToFile(this, filename, location, params)
|
||||
}
|
||||
}
|
1
packages/deno/src/common-internals-web
Symbolic link
1
packages/deno/src/common-internals-web
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../web/src/common-internals-web
|
9
packages/deno/src/index.ts
Normal file
9
packages/deno/src/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export * from './client.js'
|
||||
export * from './platform.js'
|
||||
export * from './sqlite/index.js'
|
||||
export * from './utils/crypto.js'
|
||||
export * from './utils/tcp.js'
|
||||
export * from './worker.js'
|
||||
export * from '@mtcute/core'
|
||||
export * from '@mtcute/html-parser'
|
||||
export * from '@mtcute/markdown-parser'
|
2
packages/deno/src/methods.ts
Normal file
2
packages/deno/src/methods.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { downloadToFile } from './methods/download-file.js'
|
||||
export * from '@mtcute/core/methods.js'
|
39
packages/deno/src/methods/download-file.ts
Normal file
39
packages/deno/src/methods/download-file.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { FileDownloadLocation, FileDownloadParameters, FileLocation, ITelegramClient } from '@mtcute/core'
|
||||
import { downloadAsIterable } from '@mtcute/core/methods.js'
|
||||
|
||||
import { writeAll } from '@std/io/write-all'
|
||||
|
||||
/**
|
||||
* Download a remote file to a local file (only for NodeJS).
|
||||
* Promise will resolve once the download is complete.
|
||||
*
|
||||
* @param filename Local file name to which the remote file will be downloaded
|
||||
* @param params File download parameters
|
||||
*/
|
||||
export async function downloadToFile(
|
||||
client: ITelegramClient,
|
||||
filename: string,
|
||||
location: FileDownloadLocation,
|
||||
params?: FileDownloadParameters,
|
||||
): Promise<void> {
|
||||
if (location instanceof FileLocation && ArrayBuffer.isView(location.location)) {
|
||||
// early return for inline files
|
||||
await Deno.writeFile(filename, location.location)
|
||||
}
|
||||
|
||||
const fd = await Deno.open(filename, { write: true, create: true, truncate: true })
|
||||
|
||||
if (params?.abortSignal) {
|
||||
params.abortSignal.addEventListener('abort', () => {
|
||||
client.log.debug('aborting file download %s - cleaning up', filename)
|
||||
fd.close()
|
||||
Deno.removeSync(filename)
|
||||
})
|
||||
}
|
||||
|
||||
for await (const chunk of downloadAsIterable(client, location, params)) {
|
||||
await writeAll(fd, chunk)
|
||||
}
|
||||
|
||||
fd.close()
|
||||
}
|
48
packages/deno/src/platform.ts
Normal file
48
packages/deno/src/platform.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { ICorePlatform } from '@mtcute/core/platform.js'
|
||||
|
||||
import { base64Decode, base64Encode } from './common-internals-web/base64.js'
|
||||
import { hexDecode, hexEncode } from './common-internals-web/hex.js'
|
||||
import { defaultLoggingHandler } from './common-internals-web/logging.js'
|
||||
import { utf8ByteLength, utf8Decode, utf8Encode } from './common-internals-web/utf8.js'
|
||||
import { beforeExit } from './utils/exit-hook.js'
|
||||
import { normalizeFile } from './utils/normalize-file.js'
|
||||
|
||||
export class DenoPlatform implements ICorePlatform {
|
||||
declare log: typeof defaultLoggingHandler
|
||||
declare beforeExit: typeof beforeExit
|
||||
declare normalizeFile: typeof normalizeFile
|
||||
|
||||
getDeviceModel(): string {
|
||||
return `Deno/${Deno.version.deno} (${Deno.build.os} ${Deno.build.arch})`
|
||||
}
|
||||
|
||||
getDefaultLogLevel(): number | null {
|
||||
const envLogLevel = parseInt(Deno.env.get('MTCUTE_LOG_LEVEL') ?? '')
|
||||
|
||||
if (!isNaN(envLogLevel)) {
|
||||
return envLogLevel
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// ITlPlatform
|
||||
declare utf8ByteLength: typeof utf8ByteLength
|
||||
declare utf8Encode: typeof utf8Encode
|
||||
declare utf8Decode: typeof utf8Decode
|
||||
declare hexEncode: typeof hexEncode
|
||||
declare hexDecode: typeof hexDecode
|
||||
declare base64Encode: typeof base64Encode
|
||||
declare base64Decode: typeof base64Decode
|
||||
}
|
||||
|
||||
DenoPlatform.prototype.utf8ByteLength = utf8ByteLength
|
||||
DenoPlatform.prototype.utf8Encode = utf8Encode
|
||||
DenoPlatform.prototype.utf8Decode = utf8Decode
|
||||
DenoPlatform.prototype.hexEncode = hexEncode
|
||||
DenoPlatform.prototype.hexDecode = hexDecode
|
||||
DenoPlatform.prototype.base64Encode = base64Encode
|
||||
DenoPlatform.prototype.base64Decode = base64Decode
|
||||
DenoPlatform.prototype.log = defaultLoggingHandler
|
||||
DenoPlatform.prototype.beforeExit = beforeExit
|
||||
DenoPlatform.prototype.normalizeFile = normalizeFile
|
38
packages/deno/src/sqlite/driver.ts
Normal file
38
packages/deno/src/sqlite/driver.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { BaseSqliteStorageDriver, ISqliteDatabase } from '@mtcute/core'
|
||||
|
||||
import { Database } from '@db/sqlite'
|
||||
|
||||
export interface SqliteStorageDriverOptions {
|
||||
/**
|
||||
* By default, WAL mode is enabled, which
|
||||
* significantly improves performance.
|
||||
* [Learn more](https://bun.sh/docs/api/sqlite#wal-mode)
|
||||
*
|
||||
* However, you might encounter some issues,
|
||||
* and if you do, you can disable WAL by passing `true`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disableWal?: boolean
|
||||
}
|
||||
|
||||
export class SqliteStorageDriver extends BaseSqliteStorageDriver {
|
||||
constructor(
|
||||
readonly filename = ':memory:',
|
||||
readonly params?: SqliteStorageDriverOptions,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
_createDatabase(): ISqliteDatabase {
|
||||
const db = new Database(this.filename, {
|
||||
int64: true,
|
||||
})
|
||||
|
||||
if (!this.params?.disableWal) {
|
||||
db.exec('PRAGMA journal_mode = WAL;')
|
||||
}
|
||||
|
||||
return db as ISqliteDatabase
|
||||
}
|
||||
}
|
14
packages/deno/src/sqlite/index.ts
Normal file
14
packages/deno/src/sqlite/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { BaseSqliteStorage } from '@mtcute/core'
|
||||
|
||||
import { SqliteStorageDriver, SqliteStorageDriverOptions } from './driver.js'
|
||||
|
||||
export { SqliteStorageDriver } from './driver.js'
|
||||
|
||||
export class SqliteStorage extends BaseSqliteStorage {
|
||||
constructor(
|
||||
readonly filename = ':memory:',
|
||||
readonly params?: SqliteStorageDriverOptions,
|
||||
) {
|
||||
super(new SqliteStorageDriver(filename, params))
|
||||
}
|
||||
}
|
31
packages/deno/src/sqlite/sqlite.test.ts
Normal file
31
packages/deno/src/sqlite/sqlite.test.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { afterAll, beforeAll, describe } from 'vitest'
|
||||
|
||||
import { LogManager } from '@mtcute/core/utils.js'
|
||||
import {
|
||||
testAuthKeysRepository,
|
||||
testKeyValueRepository,
|
||||
testPeersRepository,
|
||||
testRefMessagesRepository,
|
||||
} from '@mtcute/test'
|
||||
|
||||
if (import.meta.env.TEST_ENV === 'deno') {
|
||||
const { SqliteStorage } = await import('./index.js')
|
||||
|
||||
describe('SqliteStorage', () => {
|
||||
const storage = new SqliteStorage(':memory:')
|
||||
|
||||
beforeAll(async () => {
|
||||
storage.driver.setup(new LogManager())
|
||||
await storage.driver.load()
|
||||
})
|
||||
|
||||
testAuthKeysRepository(storage.authKeys)
|
||||
testKeyValueRepository(storage.kv, storage.driver)
|
||||
testPeersRepository(storage.peers, storage.driver)
|
||||
testRefMessagesRepository(storage.refMessages, storage.driver)
|
||||
|
||||
afterAll(() => storage.driver.destroy())
|
||||
})
|
||||
} else {
|
||||
describe.skip('SqliteStorage', () => {})
|
||||
}
|
1
packages/deno/src/utils.ts
Normal file
1
packages/deno/src/utils.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@mtcute/core/utils.js'
|
13
packages/deno/src/utils/crypto.test.ts
Normal file
13
packages/deno/src/utils/crypto.test.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { describe } from 'vitest'
|
||||
|
||||
import { testCryptoProvider } from '@mtcute/test'
|
||||
|
||||
if (import.meta.env.TEST_ENV === 'deno') {
|
||||
describe('DenoCryptoProvider', async () => {
|
||||
const { DenoCryptoProvider } = await import('./crypto.js')
|
||||
|
||||
testCryptoProvider(new DenoCryptoProvider())
|
||||
})
|
||||
} else {
|
||||
describe.skip('DenoCryptoProvider', () => {})
|
||||
}
|
97
packages/deno/src/utils/crypto.ts
Normal file
97
packages/deno/src/utils/crypto.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
/* eslint-disable no-restricted-globals */
|
||||
import { Buffer } from 'node:buffer'
|
||||
import { createCipheriv, createHash, createHmac, pbkdf2 } from 'node:crypto'
|
||||
import { deflateSync, gunzipSync } from 'node:zlib'
|
||||
|
||||
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from '@mtcute/core/utils.js'
|
||||
import { getWasmUrl, ige256Decrypt, ige256Encrypt, initSync } from '@mtcute/wasm'
|
||||
// node:crypto is properly implemented in deno, so we can just use it
|
||||
// largely just copy-pasting from @mtcute/node
|
||||
|
||||
const toUint8Array = (buf: Buffer | Uint8Array): Uint8Array => {
|
||||
if (!Buffer.isBuffer(buf)) return buf
|
||||
|
||||
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
||||
}
|
||||
|
||||
export class DenoCryptoProvider extends BaseCryptoProvider implements ICryptoProvider {
|
||||
async initialize(): Promise<void> {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const wasm = await fetch(getWasmUrl()).then((res) => res.arrayBuffer())
|
||||
initSync(wasm)
|
||||
}
|
||||
|
||||
createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr {
|
||||
const cipher = createCipheriv(`aes-${key.length * 8}-ctr`, key, iv)
|
||||
|
||||
const update = (data: Uint8Array) => toUint8Array(cipher.update(data))
|
||||
|
||||
return {
|
||||
process: update,
|
||||
}
|
||||
}
|
||||
|
||||
pbkdf2(
|
||||
password: Uint8Array,
|
||||
salt: Uint8Array,
|
||||
iterations: number,
|
||||
keylen = 64,
|
||||
algo = 'sha512',
|
||||
): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) =>
|
||||
pbkdf2(password, salt, iterations, keylen, algo, (err: Error | null, buf: Uint8Array) =>
|
||||
err !== null ? reject(err) : resolve(toUint8Array(buf)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sha1(data: Uint8Array): Uint8Array {
|
||||
return toUint8Array(createHash('sha1').update(data).digest())
|
||||
}
|
||||
|
||||
sha256(data: Uint8Array): Uint8Array {
|
||||
return toUint8Array(createHash('sha256').update(data).digest())
|
||||
}
|
||||
|
||||
hmacSha256(data: Uint8Array, key: Uint8Array): Uint8Array {
|
||||
return toUint8Array(createHmac('sha256', key).update(data).digest())
|
||||
}
|
||||
|
||||
createAesIge(key: Uint8Array, iv: Uint8Array): IEncryptionScheme {
|
||||
return {
|
||||
encrypt(data: Uint8Array): Uint8Array {
|
||||
return ige256Encrypt(data, key, iv)
|
||||
},
|
||||
decrypt(data: Uint8Array): Uint8Array {
|
||||
return ige256Decrypt(data, key, iv)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
gzip(data: Uint8Array, maxSize: number): Uint8Array | null {
|
||||
try {
|
||||
// telegram accepts both zlib and gzip, but zlib is faster and has less overhead, so we use it here
|
||||
return toUint8Array(
|
||||
deflateSync(data, {
|
||||
maxOutputLength: maxSize,
|
||||
}),
|
||||
)
|
||||
// hot path, avoid additional runtime checks
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ERR_BUFFER_TOO_LARGE') {
|
||||
return null
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
gunzip(data: Uint8Array): Uint8Array {
|
||||
return toUint8Array(gunzipSync(data))
|
||||
}
|
||||
|
||||
randomFill(buf: Uint8Array) {
|
||||
crypto.getRandomValues(buf)
|
||||
}
|
||||
}
|
21
packages/deno/src/utils/exit-hook.ts
Normal file
21
packages/deno/src/utils/exit-hook.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
const callbacks = new Set<() => void>()
|
||||
|
||||
let registered = false
|
||||
|
||||
export function beforeExit(fn: () => void): () => void {
|
||||
if (!registered) {
|
||||
registered = true
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
for (const callback of callbacks) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
callbacks.add(fn)
|
||||
|
||||
return () => {
|
||||
callbacks.delete(fn)
|
||||
}
|
||||
}
|
50
packages/deno/src/utils/normalize-file.ts
Normal file
50
packages/deno/src/utils/normalize-file.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { ReadStream } from 'node:fs'
|
||||
import { stat } from 'node:fs/promises'
|
||||
import { basename } from 'node:path'
|
||||
import { Readable as NodeReadable } from 'node:stream'
|
||||
|
||||
import { UploadFileLike } from '@mtcute/core'
|
||||
import { extractFileName } from '@mtcute/core/utils.js'
|
||||
|
||||
export async function normalizeFile(file: UploadFileLike) {
|
||||
if (typeof file === 'string') {
|
||||
const fd = await Deno.open(file, { read: true })
|
||||
|
||||
return {
|
||||
file: fd.readable,
|
||||
fileSize: (await fd.stat()).size,
|
||||
fileName: extractFileName(file),
|
||||
}
|
||||
}
|
||||
|
||||
if (file instanceof Deno.FsFile) {
|
||||
const stat = await file.stat()
|
||||
|
||||
return {
|
||||
file: file.readable,
|
||||
// https://github.com/denoland/deno/issues/23591
|
||||
// fileName: ...,
|
||||
fileSize: stat.size,
|
||||
}
|
||||
}
|
||||
|
||||
// while these are not Deno-specific, they still may happen
|
||||
if (file instanceof ReadStream) {
|
||||
const fileName = basename(file.path.toString())
|
||||
const fileSize = await stat(file.path.toString()).then((stat) => stat.size)
|
||||
|
||||
return {
|
||||
file: NodeReadable.toWeb(file) as unknown as ReadableStream<Uint8Array>,
|
||||
fileName,
|
||||
fileSize,
|
||||
}
|
||||
}
|
||||
|
||||
if (file instanceof NodeReadable) {
|
||||
return {
|
||||
file: NodeReadable.toWeb(file) as unknown as ReadableStream<Uint8Array>,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
135
packages/deno/src/utils/tcp.ts
Normal file
135
packages/deno/src/utils/tcp.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
import EventEmitter from 'events'
|
||||
|
||||
import { IntermediatePacketCodec, IPacketCodec, ITelegramTransport, MtcuteError, TransportState } from '@mtcute/core'
|
||||
import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js'
|
||||
|
||||
import { writeAll } from '@std/io/write-all'
|
||||
|
||||
/**
|
||||
* Base for TCP transports.
|
||||
* Subclasses must provide packet codec in `_packetCodec` property
|
||||
*/
|
||||
export abstract class BaseTcpTransport extends EventEmitter implements ITelegramTransport {
|
||||
protected _currentDc: BasicDcOption | null = null
|
||||
protected _state: TransportState = TransportState.Idle
|
||||
protected _socket: Deno.TcpConn | null = null
|
||||
|
||||
abstract _packetCodec: IPacketCodec
|
||||
protected _crypto!: ICryptoProvider
|
||||
protected log!: Logger
|
||||
|
||||
packetCodecInitialized = false
|
||||
|
||||
private _updateLogPrefix() {
|
||||
if (this._currentDc) {
|
||||
this.log.prefix = `[TCP:${this._currentDc.ipAddress}:${this._currentDc.port}] `
|
||||
} else {
|
||||
this.log.prefix = '[TCP:disconnected] '
|
||||
}
|
||||
}
|
||||
|
||||
setup(crypto: ICryptoProvider, log: Logger): void {
|
||||
this._crypto = crypto
|
||||
this.log = log.create('tcp')
|
||||
this._updateLogPrefix()
|
||||
}
|
||||
|
||||
state(): TransportState {
|
||||
return this._state
|
||||
}
|
||||
|
||||
currentDc(): BasicDcOption | null {
|
||||
return this._currentDc
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
connect(dc: BasicDcOption, testMode: boolean): void {
|
||||
if (this._state !== TransportState.Idle) {
|
||||
throw new MtcuteError('Transport is not IDLE')
|
||||
}
|
||||
|
||||
if (!this.packetCodecInitialized) {
|
||||
this._packetCodec.setup?.(this._crypto, this.log)
|
||||
this._packetCodec.on('error', (err) => this.emit('error', err))
|
||||
this._packetCodec.on('packet', (buf) => this.emit('message', buf))
|
||||
this.packetCodecInitialized = true
|
||||
}
|
||||
|
||||
this._state = TransportState.Connecting
|
||||
this._currentDc = dc
|
||||
this._updateLogPrefix()
|
||||
|
||||
this.log.debug('connecting to %j', dc)
|
||||
|
||||
Deno.connect({
|
||||
hostname: dc.ipAddress,
|
||||
port: dc.port,
|
||||
transport: 'tcp',
|
||||
})
|
||||
.then(this.handleConnect.bind(this))
|
||||
.catch((err) => {
|
||||
this.handleError(err)
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this._state === TransportState.Idle) return
|
||||
this.log.info('connection closed')
|
||||
|
||||
this._state = TransportState.Idle
|
||||
this._socket?.close()
|
||||
this._socket = null
|
||||
this._currentDc = null
|
||||
this._packetCodec.reset()
|
||||
this.emit('close')
|
||||
}
|
||||
|
||||
handleError(error: unknown): void {
|
||||
this.log.error('error: %s', error)
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
||||
async handleConnect(socket: Deno.TcpConn): Promise<void> {
|
||||
this._socket = socket
|
||||
this.log.info('connected')
|
||||
|
||||
try {
|
||||
const packet = await this._packetCodec.tag()
|
||||
|
||||
if (packet.length) {
|
||||
await writeAll(this._socket, packet)
|
||||
}
|
||||
|
||||
this._state = TransportState.Ready
|
||||
this.emit('ready')
|
||||
|
||||
const reader = this._socket.readable.getReader()
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
this._packetCodec.feed(value)
|
||||
}
|
||||
} catch (e) {
|
||||
this.handleError(e)
|
||||
}
|
||||
|
||||
this.close()
|
||||
}
|
||||
|
||||
async send(bytes: Uint8Array): Promise<void> {
|
||||
const framed = await this._packetCodec.encode(bytes)
|
||||
|
||||
if (this._state !== TransportState.Ready) {
|
||||
throw new MtcuteError('Transport is not READY')
|
||||
}
|
||||
|
||||
await writeAll(this._socket!, framed)
|
||||
}
|
||||
}
|
||||
|
||||
export class TcpTransport extends BaseTcpTransport {
|
||||
_packetCodec = new IntermediatePacketCodec()
|
||||
}
|
71
packages/deno/src/worker.ts
Normal file
71
packages/deno/src/worker.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/// <reference lib="WebWorker" />
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import {
|
||||
ClientMessageHandler,
|
||||
RespondFn,
|
||||
SendFn,
|
||||
SomeWorker,
|
||||
TelegramWorker as TelegramWorkerBase,
|
||||
TelegramWorkerOptions,
|
||||
TelegramWorkerPort as TelegramWorkerPortBase,
|
||||
TelegramWorkerPortOptions,
|
||||
WorkerCustomMethods,
|
||||
WorkerMessageHandler,
|
||||
} from '@mtcute/core/worker.js'
|
||||
|
||||
import { DenoPlatform } from './platform.js'
|
||||
|
||||
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
|
||||
|
||||
let _registered = false
|
||||
|
||||
export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorkerBase<T> {
|
||||
registerWorker(handler: WorkerMessageHandler): RespondFn {
|
||||
if (_registered) {
|
||||
throw new Error('TelegramWorker must be created only once')
|
||||
}
|
||||
|
||||
_registered = true
|
||||
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||
const respond: RespondFn = self.postMessage.bind(self)
|
||||
|
||||
// eslint-disable-next-line
|
||||
self.addEventListener('message', (message) => handler((message as any).data, respond))
|
||||
|
||||
return respond
|
||||
}
|
||||
|
||||
throw new Error('TelegramWorker must be created from a worker')
|
||||
}
|
||||
}
|
||||
|
||||
const platform = new DenoPlatform()
|
||||
|
||||
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
|
||||
constructor(readonly options: TelegramWorkerPortOptions) {
|
||||
setPlatform(platform)
|
||||
super(options)
|
||||
}
|
||||
|
||||
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
||||
if (worker instanceof Worker) {
|
||||
const send: SendFn = worker.postMessage.bind(worker)
|
||||
|
||||
const messageHandler = (ev: MessageEvent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
handler(ev.data)
|
||||
}
|
||||
|
||||
worker.addEventListener('message', messageHandler)
|
||||
|
||||
return [
|
||||
send,
|
||||
() => {
|
||||
worker.removeEventListener('message', messageHandler)
|
||||
},
|
||||
]
|
||||
}
|
||||
throw new Error('Only workers are supported')
|
||||
}
|
||||
}
|
16
packages/deno/tsconfig.json
Normal file
16
packages/deno/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
},
|
||||
"include": [
|
||||
"./src",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../dispatcher" },
|
||||
{ "path": "../html-parser" },
|
||||
{ "path": "../markdown-parser" }
|
||||
]
|
||||
}
|
10
packages/deno/typedoc.cjs
Normal file
10
packages/deno/typedoc.cjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
extends: ['../../.config/typedoc/config.base.cjs'],
|
||||
entryPoints: ['./src/index.ts'],
|
||||
externalPattern: [
|
||||
'../core/**',
|
||||
'../html-parser/**',
|
||||
'../markdown-parser/**',
|
||||
'../sqlite/**',
|
||||
],
|
||||
}
|
|
@ -6,5 +6,8 @@
|
|||
},
|
||||
"include": [
|
||||
"./src",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export class NodePlatform implements ICorePlatform {
|
|||
declare normalizeFile: typeof normalizeFile
|
||||
|
||||
getDeviceModel(): string {
|
||||
return `${os.type()} ${os.arch()} ${os.release()}`
|
||||
return `Node.js/${process.version} (${os.type()} ${os.arch()})`
|
||||
}
|
||||
|
||||
getDefaultLogLevel(): number | null {
|
||||
|
|
|
@ -4,7 +4,6 @@ import { readFile } from 'fs/promises'
|
|||
import { createRequire } from 'module'
|
||||
import { deflateSync, gunzipSync } from 'zlib'
|
||||
|
||||
import { MaybePromise } from '@mtcute/core'
|
||||
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from '@mtcute/core/utils.js'
|
||||
import { ige256Decrypt, ige256Encrypt, initSync } from '@mtcute/wasm'
|
||||
|
||||
|
@ -25,7 +24,7 @@ export abstract class BaseNodeCryptoProvider extends BaseCryptoProvider {
|
|||
iterations: number,
|
||||
keylen = 64,
|
||||
algo = 'sha512',
|
||||
): MaybePromise<Uint8Array> {
|
||||
): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) =>
|
||||
pbkdf2(password, salt, iterations, keylen, algo, (err: Error | null, buf: Uint8Array) =>
|
||||
err !== null ? reject(err) : resolve(buf),
|
||||
|
|
2
packages/web/src/common-internals-web/readme.md
Normal file
2
packages/web/src/common-internals-web/readme.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
this folder is for common code across `@mtcute/web` and `@mtcute/deno`.
|
||||
it is symlinked into `@mtcute/deno`
|
|
@ -9,7 +9,7 @@ export function utf8ByteLength(str: string) {
|
|||
const code = str.charCodeAt(i)
|
||||
if (code > 0x7f && code <= 0x7ff) s++
|
||||
else if (code > 0x7ff && code <= 0xffff) s += 2
|
||||
if (code >= 0xDC00 && code <= 0xDFFF) i-- //trail surrogate
|
||||
if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate
|
||||
}
|
||||
|
||||
return s
|
|
@ -1,3 +1,5 @@
|
|||
/// <reference lib="dom" />
|
||||
/// <reference lib="dom.iterable" />
|
||||
import { BaseStorageDriver, MtUnsupportedError } from '@mtcute/core'
|
||||
|
||||
import { txToPromise } from './utils.js'
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { ICorePlatform } from '@mtcute/core/platform.js'
|
||||
|
||||
import { base64Decode, base64Encode } from './encodings/base64.js'
|
||||
import { hexDecode, hexEncode } from './encodings/hex.js'
|
||||
import { utf8ByteLength, utf8Decode, utf8Encode } from './encodings/utf8.js'
|
||||
import { base64Decode, base64Encode } from './common-internals-web/base64.js'
|
||||
import { hexDecode, hexEncode } from './common-internals-web/hex.js'
|
||||
import { defaultLoggingHandler } from './common-internals-web/logging.js'
|
||||
import { utf8ByteLength, utf8Decode, utf8Encode } from './common-internals-web/utf8.js'
|
||||
import { beforeExit } from './exit-hook.js'
|
||||
import { defaultLoggingHandler } from './logging.js'
|
||||
|
||||
export class WebPlatform implements ICorePlatform {
|
||||
// ICorePlatform
|
||||
|
@ -52,7 +52,6 @@ export class WebPlatform implements ICorePlatform {
|
|||
declare utf8Decode: typeof utf8Decode
|
||||
declare hexEncode: typeof hexEncode
|
||||
declare hexDecode: typeof hexDecode
|
||||
|
||||
declare base64Encode: typeof base64Encode
|
||||
declare base64Decode: typeof base64Decode
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/// <reference lib="dom" />
|
||||
/// <reference lib="webworker" />
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import {
|
||||
ClientMessageHandler,
|
||||
|
|
105
pnpm-lock.yaml
105
pnpm-lock.yaml
|
@ -231,6 +231,31 @@ importers:
|
|||
specifier: workspace:^
|
||||
version: link:../test
|
||||
|
||||
packages/deno:
|
||||
dependencies:
|
||||
'@db/sqlite':
|
||||
specifier: npm:@jsr/db__sqlite@0.11.1
|
||||
version: /@jsr/db__sqlite@0.11.1
|
||||
'@mtcute/core':
|
||||
specifier: workspace:^
|
||||
version: link:../core
|
||||
'@mtcute/html-parser':
|
||||
specifier: workspace:^
|
||||
version: link:../html-parser
|
||||
'@mtcute/markdown-parser':
|
||||
specifier: workspace:^
|
||||
version: link:../markdown-parser
|
||||
'@mtcute/wasm':
|
||||
specifier: workspace:^
|
||||
version: link:../wasm
|
||||
'@std/io':
|
||||
specifier: npm:@jsr/std__io@0.223.0
|
||||
version: /@jsr/std__io@0.223.0
|
||||
devDependencies:
|
||||
'@mtcute/test':
|
||||
specifier: workspace:^
|
||||
version: link:../test
|
||||
|
||||
packages/dispatcher:
|
||||
dependencies:
|
||||
'@mtcute/core':
|
||||
|
@ -1189,6 +1214,86 @@ packages:
|
|||
'@jridgewell/sourcemap-codec': 1.4.11
|
||||
dev: true
|
||||
|
||||
/@jsr/db__sqlite@0.11.1:
|
||||
resolution: {integrity: sha512-6IKyfi+TQan431kwOy3WrdzgKwITuDdSKfq6nkWINfNWknPQ+lQ6/R008bAUUz+AwW8e0prxi3IMMxzELUV8Lw==, tarball: https://npm.jsr.io/~/8/@jsr/db__sqlite/0.11.1.tgz}
|
||||
dependencies:
|
||||
'@jsr/denosaurs__plug': 1.0.6
|
||||
'@jsr/std__path': 0.217.0
|
||||
dev: false
|
||||
|
||||
/@jsr/denosaurs__plug@1.0.6:
|
||||
resolution: {integrity: sha512-2uqvX2xpDy5W76jJVKazXvHuh5WPNg8eUV+2u+Hcn5XLwKqWGr/xj4wQFRMXrS12Xhya+ToZdUg4gxLh+XOOCg==, tarball: https://npm.jsr.io/~/8/@jsr/denosaurs__plug/1.0.6.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__encoding': 0.221.0
|
||||
'@jsr/std__fmt': 0.221.0
|
||||
'@jsr/std__fs': 0.221.0
|
||||
'@jsr/std__path': 0.221.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__assert@0.217.0:
|
||||
resolution: {integrity: sha512-RCQbXJeUVCgDGEPsrO57CI9Cgbo9NAWsJUhZ7vrHgtD//Ic32YmUQazdGKPZzao5Zn8dP6xV4Nma3HHZC5ySTw==, tarball: https://npm.jsr.io/~/8/@jsr/std__assert/0.217.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__fmt': 0.217.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__assert@0.221.0:
|
||||
resolution: {integrity: sha512-2B+5fq4Rar8NmLms7sv9YfYlMukZDTNMQV5fXjtZvnaKc8ljt+59UsMtIjTXFsDwsAx7VoxkMKmmdHxlP+h5JA==, tarball: https://npm.jsr.io/~/8/@jsr/std__assert/0.221.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__fmt': 0.221.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__assert@0.223.0:
|
||||
resolution: {integrity: sha512-9FWOoAQN1uF5SliWw3IgdXmk2usz5txvausX4sLAASHfQMbUSCe1akcD7HgFV01J/2Mr9TfCjPvsSUzuuASouQ==, tarball: https://npm.jsr.io/~/8/@jsr/std__assert/0.223.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__fmt': 0.223.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__bytes@0.223.0:
|
||||
resolution: {integrity: sha512-BBjhj0uFlB3+AVEmaPygEwY5CL5mj3vSZlusC8xxjCRNWDYGukfQT/F5GOTTfjeaq7njduk7TYe6e5cDg659yg==, tarball: https://npm.jsr.io/~/8/@jsr/std__bytes/0.223.0.tgz}
|
||||
dev: false
|
||||
|
||||
/@jsr/std__encoding@0.221.0:
|
||||
resolution: {integrity: sha512-FT5i/WHNtXJvqOITDK0eOVIyyOphqtxwhzo5PiVWoYTFmUuFcRYKas39GT1UQDi4s24FcHd2deQEBbi3tPAj1Q==, tarball: https://npm.jsr.io/~/8/@jsr/std__encoding/0.221.0.tgz}
|
||||
dev: false
|
||||
|
||||
/@jsr/std__fmt@0.217.0:
|
||||
resolution: {integrity: sha512-L3mVYP7DsujrJ001SvPr4Fl/Fu0e3uzgHJ6NYTRUk7sgi9k7YKeLOLVwRijUX7qIsp3Ourp2DyAHHgYDgT4GcQ==, tarball: https://npm.jsr.io/~/8/@jsr/std__fmt/0.217.0.tgz}
|
||||
dev: false
|
||||
|
||||
/@jsr/std__fmt@0.221.0:
|
||||
resolution: {integrity: sha512-VLqM052U78LQ11p/KfqI49a2/sDbKtHFHuxO/h+3Cnvhze9beIZU4Lg3Gpu8rGYjB2YS6CfXzKXHuyAJn5FJFg==, tarball: https://npm.jsr.io/~/8/@jsr/std__fmt/0.221.0.tgz}
|
||||
dev: false
|
||||
|
||||
/@jsr/std__fmt@0.223.0:
|
||||
resolution: {integrity: sha512-J6SVTw/l3C4hOwEuqnZ4ZHD1jVIIZt09fb5LP9CMGyVGNnoW8/lxJvCNhIOv+3ZXC1ErGlIzW4bgYSxHwbvSaQ==, tarball: https://npm.jsr.io/~/8/@jsr/std__fmt/0.223.0.tgz}
|
||||
dev: false
|
||||
|
||||
/@jsr/std__fs@0.221.0:
|
||||
resolution: {integrity: sha512-2XMlO67zQlKoxbCsfGOBVlnyWhMMdOzYUWfajvggfw2p+yITd9hJj9+tpfiwLf/88CzknhlMLwSCamSYjHKloA==, tarball: https://npm.jsr.io/~/8/@jsr/std__fs/0.221.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__assert': 0.221.0
|
||||
'@jsr/std__path': 0.221.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__io@0.223.0:
|
||||
resolution: {integrity: sha512-K+OXJHsIf9227aYgNTaapEkpphHrI+oYVkl14UV+le+Fk9MzkJmebU0XAU6krgVS283mW7VPJsXVV3gD5JWvJw==, tarball: https://npm.jsr.io/~/8/@jsr/std__io/0.223.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__assert': 0.223.0
|
||||
'@jsr/std__bytes': 0.223.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__path@0.217.0:
|
||||
resolution: {integrity: sha512-OkP+yiBJpFZKTH3gHqlepYU3TQzXM/UjEQ0U1gYw8BwVr87TwKfzwAb1WT1vY/Bs8NXScvuP4Kpu/UhEsNHD3A==, tarball: https://npm.jsr.io/~/8/@jsr/std__path/0.217.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__assert': 0.217.0
|
||||
dev: false
|
||||
|
||||
/@jsr/std__path@0.221.0:
|
||||
resolution: {integrity: sha512-uOWaY4cWp28CFBSisr8M/92FtpyjiFO0+wQSH7GgmiXQUls+vALqdCGewFkunG8RfA/25RGdot5hFXedmtPdOg==, tarball: https://npm.jsr.io/~/8/@jsr/std__path/0.221.0.tgz}
|
||||
dependencies:
|
||||
'@jsr/std__assert': 0.221.0
|
||||
dev: false
|
||||
|
||||
/@ljharb/through@2.3.11:
|
||||
resolution: {integrity: sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
|
@ -499,6 +499,10 @@ if (IS_JSR) {
|
|||
for (const [name, version] of Object.entries(builtPkgJson.dependencies)) {
|
||||
if (name.startsWith('@mtcute/')) {
|
||||
importMap[name] = `jsr:${name}@${version}`
|
||||
} else if (version.startsWith('npm:@jsr/')) {
|
||||
const jsrName = version.slice(9).split('@')[0].replace('__', '/')
|
||||
const jsrVersion = version.slice(9).split('@')[1]
|
||||
importMap[name] = `jsr:@${jsrName}@${jsrVersion}`
|
||||
} else {
|
||||
importMap[name] = `npm:${name}@${version}`
|
||||
}
|
||||
|
|
33
scripts/fetch-deno-dts.mjs
Normal file
33
scripts/fetch-deno-dts.mjs
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createHash } from 'crypto'
|
||||
import * as fs from 'fs/promises'
|
||||
import { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const DTS_URL = 'https://github.com/denoland/deno/releases/download/v1.42.4/lib.deno.d.ts'
|
||||
const SHA256 = '554b5da7baf05e5693ca064fcf1665b0b847743ccfd0db89cb6f2388f2de0276'
|
||||
const LIB_TARGET = fileURLToPath(new URL('../node_modules/@types/deno/index.d.ts', import.meta.url))
|
||||
|
||||
const stat = await fs.stat(LIB_TARGET).catch(() => null)
|
||||
|
||||
if (stat?.isFile()) {
|
||||
const sha256 = createHash('sha256').update(await fs.readFile(LIB_TARGET)).digest('hex')
|
||||
|
||||
if (sha256 === SHA256) {
|
||||
console.log('lib.deno.d.ts is up to date')
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
const stream = await fetch(DTS_URL)
|
||||
const dts = await stream.text()
|
||||
|
||||
const sha256 = createHash('sha256').update(dts).digest('hex')
|
||||
|
||||
if (sha256 !== SHA256) {
|
||||
console.error(`lib.deno.d.ts SHA256 mismatch: expected ${SHA256}, got ${sha256}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await fs.mkdir(dirname(LIB_TARGET), { recursive: true }).catch(() => null)
|
||||
await fs.writeFile(LIB_TARGET, dts)
|
||||
console.log('lib.deno.d.ts updated')
|
|
@ -25,6 +25,7 @@ const JSR_EXCEPTIONS = {
|
|||
bun: 'never',
|
||||
'create-bot': 'never',
|
||||
'crypto-node': 'never',
|
||||
deno: 'only',
|
||||
node: 'never',
|
||||
'http-proxy': 'never',
|
||||
'socks-proxy': 'never',
|
||||
|
|
23
scripts/remove-jsr-sourcefiles.mjs
Normal file
23
scripts/remove-jsr-sourcefiles.mjs
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as fs from 'fs'
|
||||
import { globSync } from 'glob'
|
||||
import { join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// for whatever reason, jsr's npm compatibility jayer doesn't remove
|
||||
// original typescript source files, which results in type errors when
|
||||
// trying to build the project. this script removes all source files from @jsr/*
|
||||
// https://discord.com/channels/684898665143206084/1203185670508515399/1234222204044967967
|
||||
|
||||
const nodeModules = fileURLToPath(new URL('../node_modules', import.meta.url))
|
||||
|
||||
let count = 0
|
||||
|
||||
for (const file of globSync(join(nodeModules, '.pnpm/**/node_modules/@jsr/**/*.ts'))) {
|
||||
if (file.endsWith('.d.ts')) continue
|
||||
if (!fs.existsSync(file)) continue
|
||||
|
||||
fs.unlinkSync(file)
|
||||
count++
|
||||
}
|
||||
|
||||
console.log(`[jsr] removed ${count} source files`)
|
|
@ -19,6 +19,7 @@
|
|||
"composite": true,
|
||||
"types": [
|
||||
"node",
|
||||
"deno",
|
||||
"vite/client"
|
||||
],
|
||||
"lib": [
|
||||
|
|
Loading…
Reference in a new issue