snapshot of https://github.com/ueberdosis/hocuspocus.git @ cf3cfa355b4d3bb61c6b6c80071f71783e32470e (2026-09-15)
Some checks failed
build / lint (22) (push) Failing after 17s
build / test (22) (push) Failing after 8s
build / lint (23) (push) Failing after 2s
build / build (22) (push) Has been skipped
build / build (23) (push) Has been skipped
build / test (23) (push) Failing after 4s
docsearch / docsearch (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 17:21:35 +05:00
commit e0c588ca1c
265 changed files with 55259 additions and 0 deletions

View File

@@ -0,0 +1,337 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [4.7.0](https://github.com/ueberdosis/hocuspocus/compare/v4.6.0...v4.7.0) (2026-09-09)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.6.0](https://github.com/ueberdosis/hocuspocus/compare/v4.5.0...v4.6.0) (2026-08-10)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.5.0](https://github.com/ueberdosis/hocuspocus/compare/v4.4.0...v4.5.0) (2026-08-04)
### Bug Fixes
* update packages via audit --fix ([1dc9ca0](https://github.com/ueberdosis/hocuspocus/commit/1dc9ca0ff35f1033136473d134cee8cb6b336281))
# [4.4.0](https://github.com/ueberdosis/hocuspocus/compare/v4.3.0...v4.4.0) (2026-07-13)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.3.0](https://github.com/ueberdosis/hocuspocus/compare/v4.2.0...v4.3.0) (2026-06-18)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.2.0](https://github.com/ueberdosis/hocuspocus/compare/v4.1.2...v4.2.0) (2026-06-12)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [4.1.2](https://github.com/ueberdosis/hocuspocus/compare/v4.1.1...v4.1.2) (2026-06-11)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [4.1.1](https://github.com/ueberdosis/hocuspocus/compare/v4.1.0...v4.1.1) (2026-06-10)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.1.0](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0...v4.1.0) (2026-05-20)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.7...v4.0.0) (2026-04-23)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.7](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.6...v4.0.0-rc.7) (2026-04-22)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.6](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.5...v4.0.0-rc.6) (2026-04-20)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.5](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.4...v4.0.0-rc.5) (2026-04-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.4](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.3...v4.0.0-rc.4) (2026-04-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.3](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.2...v4.0.0-rc.3) (2026-04-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.2](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.1...v4.0.0-rc.2) (2026-04-08)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.1](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.0...v4.0.0-rc.1) (2026-03-30)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.0](https://github.com/ueberdosis/hocuspocus/compare/v3.4.4...v4.0.0-rc.0) (2026-03-16)
### Features
* migrate to pnpm ([#1061](https://github.com/ueberdosis/hocuspocus/issues/1061)) ([dcaab57](https://github.com/ueberdosis/hocuspocus/commit/dcaab571bad694e48efd41bbb1d675855a7e2055))
# [4.0.0-rc.5](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.4...v4.0.0-rc.5) (2026-03-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.4](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.3...v4.0.0-rc.4) (2026-03-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.3](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.2...v4.0.0-rc.3) (2026-03-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.2](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.1...v4.0.0-rc.2) (2026-03-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.1](https://github.com/ueberdosis/hocuspocus/compare/v4.0.0-rc.0...v4.0.0-rc.1) (2026-03-16)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [4.0.0-rc.0](https://github.com/ueberdosis/hocuspocus/compare/v3.4.4...v4.0.0-rc.0) (2026-02-18)
### Features
* migrate to pnpm ([#1061](https://github.com/ueberdosis/hocuspocus/issues/1061)) ([dcaab57](https://github.com/ueberdosis/hocuspocus/commit/dcaab571bad694e48efd41bbb1d675855a7e2055))
## [3.4.6-rc.2](https://github.com/ueberdosis/hocuspocus/compare/v3.4.6-rc.1...v3.4.6-rc.2) (2026-02-03)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.6-rc.1](https://github.com/ueberdosis/hocuspocus/compare/v3.4.6-rc.0...v3.4.6-rc.1) (2026-02-03)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.6-rc.0](https://github.com/ueberdosis/hocuspocus/compare/v3.4.5-rc.0...v3.4.6-rc.0) (2026-02-03)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.5-rc.0](https://github.com/ueberdosis/hocuspocus/compare/v3.4.4...v3.4.5-rc.0) (2026-02-03)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.4](https://github.com/ueberdosis/hocuspocus/compare/v3.4.3...v3.4.4) (2026-01-25)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.3](https://github.com/ueberdosis/hocuspocus/compare/v3.4.2...v3.4.3) (2025-12-15)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.2](https://github.com/ueberdosis/hocuspocus/compare/v3.4.1...v3.4.2) (2025-12-15)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.4.1](https://github.com/ueberdosis/hocuspocus/compare/v3.4.0...v3.4.1) (2025-12-09)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [3.4.0](https://github.com/ueberdosis/hocuspocus/compare/v3.3.2...v3.4.0) (2025-10-24)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.3.2](https://github.com/ueberdosis/hocuspocus/compare/v3.3.1...v3.3.2) (2025-10-23)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.3.1](https://github.com/ueberdosis/hocuspocus/compare/v3.3.0...v3.3.1) (2025-10-22)
**Note:** Version bump only for package @hocuspocus/extension-s3
# [3.3.0](https://github.com/ueberdosis/hocuspocus/compare/v3.2.6...v3.3.0) (2025-10-22)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.2.6](https://github.com/ueberdosis/hocuspocus/compare/v3.2.5...v3.2.6) (2025-10-17)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.2.5](https://github.com/ueberdosis/hocuspocus/compare/v3.2.4...v3.2.5) (2025-10-07)
**Note:** Version bump only for package @hocuspocus/extension-s3
## [3.2.4](https://github.com/ueberdosis/hocuspocus/compare/v3.2.3...v3.2.4) (2025-09-30)
**Note:** Version bump only for package @hocuspocus/extension-s3
# Changelog
## [3.2.3] - 2024-08-10
### Added
- Initial release of @hocuspocus/extension-s3
- Support for Amazon S3 and S3-compatible storage services (MinIO, DigitalOcean Spaces, etc.)
- Configurable bucket, region, and key prefix
- Support for custom S3 clients
- IAM role support for AWS environments
- Proper error handling for missing documents

View File

@@ -0,0 +1,171 @@
# @hocuspocus/extension-s3
[![Version](https://img.shields.io/npm/v/@hocuspocus/extension-s3.svg?label=version)](https://www.npmjs.com/package/@hocuspocus/extension-s3)
[![Downloads](https://img.shields.io/npm/dm/@hocuspocus/extension-s3.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![License](https://img.shields.io/npm/l/@hocuspocus/extension-s3.svg)](https://www.npmjs.com/package/@hocuspocus/extension-s3)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
A S3-compatible persistence driver for [Hocuspocus](https://github.com/ueberdosis/hocuspocus). Stores Y.js documents in Amazon S3 or S3-compatible storage (MinIO, DigitalOcean Spaces, Cloudflare R2, Backblaze B2, etc.).
## Installation
```bash
npm install @hocuspocus/extension-s3
```
## Quick Start
### Basic AWS S3 Configuration
```javascript
import { Server } from '@hocuspocus/server'
import { S3 } from '@hocuspocus/extension-s3'
const server = new Server({
extensions: [
new S3({
bucket: 'my-documents-bucket',
region: 'us-east-1',
credentials: {
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key'
}
}),
],
})
server.listen()
```
### MinIO Configuration
For local development with MinIO, ensure you set `forcePathStyle: true`:
```javascript
const server = new Server({
extensions: [
new S3({
bucket: 'hocuspocus-documents',
endpoint: 'http://localhost:9000',
forcePathStyle: true,
credentials: {
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin'
}
}),
],
})
```
## Configuration Options
- **bucket** (required): S3 bucket name where documents will be stored
- **region**: AWS region (default: 'us-east-1')
- **prefix**: Key prefix for documents (default: 'hocuspocus-documents/')
- **credentials**: AWS credentials object with accessKeyId and secretAccessKey
- **endpoint**: S3 endpoint URL (for S3-compatible services)
- **forcePathStyle**: Use path-style URLs (required for MinIO, default: false)
- **s3Client**: Custom S3Client instance
## Document Storage
Documents are stored as binary files in S3 with the naming convention:
`{prefix}{documentName}.bin`
## Scaling with Redis
For horizontal scaling, combine S3 with the Redis extension. Redis handles real-time synchronization while S3 provides persistent storage.
```javascript
import { Server } from '@hocuspocus/server'
import { Logger } from '@hocuspocus/extension-logger'
import { Redis } from '@hocuspocus/extension-redis'
import { S3 } from '@hocuspocus/extension-s3'
// Server 1
const server1 = new Server({
name: "server-1",
port: 8001,
extensions: [
new Logger(),
new Redis({
host: "127.0.0.1",
port: 6379,
}),
new S3({
bucket: 'hocuspocus-documents',
endpoint: 'http://localhost:9000', // MinIO
forcePathStyle: true,
credentials: {
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin'
}
}),
],
})
// Server 2 - same configuration with different name/port
const server2 = new Server({
name: "server-2",
port: 8002,
extensions: [/* same extensions */],
})
server1.listen()
server2.listen()
```
## CLI Usage
You can also use the S3 extension with the Hocuspocus CLI:
```bash
# Basic usage
hocuspocus --s3 --s3-bucket my-documents
# MinIO setup (forcePathStyle automatically enabled)
hocuspocus --s3 --s3-bucket hocuspocus-documents --s3-endpoint http://localhost:9000
```
## Development
### Quick Start
For local development with MinIO (S3-compatible storage):
```bash
# Set up development environment
npm run dev:setup
# Test S3 configuration
npm run dev:test-s3
# Run S3 playground examples
npm run playground:s3
```
## Complete Documentation
For detailed documentation, examples, best practices, and troubleshooting, see:
[📚 S3 Extension Documentation](https://tiptap.dev/docs/hocuspocus/server/extensions/s3)
## IAM Permissions
Your AWS credentials or IAM role needs the following permissions:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:HeadObject"],
"Resource": "arn:aws:s3:::your-bucket-name/*"
},
{
"Effect": "Allow",
"Action": ["s3:HeadBucket"],
"Resource": "arn:aws:s3:::your-bucket-name"
}
]
}
```

View File

@@ -0,0 +1,42 @@
{
"name": "@hocuspocus/extension-s3",
"description": "a S3-compatible persistence driver for Hocuspocus",
"version": "4.7.0",
"homepage": "https://hocuspocus.dev",
"keywords": [
"hocuspocus",
"yjs",
"s3",
"persistence"
],
"license": "MIT",
"type": "module",
"main": "dist/hocuspocus-s3.cjs",
"module": "dist/hocuspocus-s3.esm.js",
"types": "dist/index.d.ts",
"exports": {
"source": {
"import": "./src/index.ts"
},
"default": {
"import": "./dist/hocuspocus-s3.esm.js",
"require": "./dist/hocuspocus-s3.cjs",
"types": "./dist/index.d.ts"
}
},
"files": [
"src",
"dist"
],
"dependencies": {
"@aws-sdk/client-s3": "^3.990.0",
"@hocuspocus/extension-database": "workspace:^",
"kleur": "^4.1.5"
},
"publishConfig": {
"access": "public"
},
"repository": {
"url": "https://github.com/ueberdosis/hocuspocus"
}
}

View File

@@ -0,0 +1,197 @@
import type { DatabaseConfiguration } from "@hocuspocus/extension-database";
import { Database } from "@hocuspocus/extension-database";
import {
S3Client,
GetObjectCommand,
PutObjectCommand,
HeadObjectCommand,
} from "@aws-sdk/client-s3";
import kleur from "kleur";
export interface S3Configuration extends DatabaseConfiguration {
/**
* AWS S3 region
*/
region?: string;
/**
* S3 bucket name
*/
bucket: string;
/**
* S3 key prefix for documents (optional)
*/
prefix?: string;
/**
* AWS credentials
*/
credentials?: {
accessKeyId: string;
secretAccessKey: string;
};
/**
* S3 endpoint URL (for S3-compatible services like MinIO)
*/
endpoint?: string;
/**
* Force path style URLs (required for MinIO)
*/
forcePathStyle?: boolean;
/**
* Custom S3 client
*/
s3Client?: S3Client;
}
export class S3 extends Database {
private s3Client?: S3Client;
configuration: S3Configuration = {
region: "us-east-1",
bucket: "",
prefix: "hocuspocus-documents/",
forcePathStyle: false,
fetch: async ({ documentName }) => {
const key = this.getObjectKey(documentName);
try {
const command = new GetObjectCommand({
Bucket: this.configuration.bucket,
Key: key,
});
const response = await this.s3Client!.send(command);
if (response.Body) {
// Convert stream to Uint8Array
const chunks: Uint8Array[] = [];
const reader = response.Body.transformToWebStream().getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
// Combine all chunks into a single Uint8Array
const totalLength = chunks.reduce(
(acc, chunk) => acc + chunk.length,
0,
);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
return null;
} catch (error: any) {
if (
error.name === "NoSuchKey" ||
error.$metadata?.httpStatusCode === 404
) {
// Document doesn't exist yet, return null
return null;
}
throw error;
}
},
store: async ({ documentName, state }) => {
const key = this.getObjectKey(documentName);
const command = new PutObjectCommand({
Bucket: this.configuration.bucket,
Key: key,
Body: state,
ContentType: "application/octet-stream",
});
await this.s3Client!.send(command);
},
};
constructor(configuration: Partial<S3Configuration>) {
super({});
this.configuration = {
...this.configuration,
...configuration,
};
// Validate required configuration
if (!this.configuration.bucket) {
throw new Error("S3 bucket name is required");
}
}
private getObjectKey(documentName: string): string {
const prefix = this.configuration.prefix || "";
return `${prefix}${documentName}.bin`;
}
async onConfigure() {
// Use custom S3 client if provided, otherwise create one
if (this.configuration.s3Client) {
this.s3Client = this.configuration.s3Client;
} else {
const clientConfig: any = {
region: this.configuration.region,
};
if (this.configuration.credentials) {
clientConfig.credentials = this.configuration.credentials;
}
if (this.configuration.endpoint) {
clientConfig.endpoint = this.configuration.endpoint;
clientConfig.forcePathStyle = this.configuration.forcePathStyle;
}
this.s3Client = new S3Client(clientConfig);
}
// Test S3 connection by checking if bucket exists
try {
const command = new HeadObjectCommand({
Bucket: this.configuration.bucket,
Key: "test-connection", // This will likely return 404, but that's fine
});
await this.s3Client.send(command);
} catch (error: any) {
// 404 is expected for the test key, any other error indicates connection issues
if (error.$metadata?.httpStatusCode !== 404) {
// Don't show credential errors as connection failures in development
if (error.message?.includes("Could not load credentials")) {
console.warn(` ${kleur.yellow("S3 warning:")} ${error.message}`);
console.warn(
` ${kleur.yellow("Note:")} Ensure AWS credentials are properly configured for production use`,
);
} else {
console.error(
` ${kleur.red("S3 connection failed:")} ${error.message}`,
);
}
}
}
}
async onListen() {
const endpoint =
this.configuration.endpoint ||
`https://s3.${this.configuration.region}.amazonaws.com`;
console.log(
` ${kleur.green("S3 extension configured:")} bucket=${this.configuration.bucket}, endpoint=${endpoint}`,
);
if (this.configuration.prefix) {
console.log(
` ${kleur.blue("S3 key prefix:")} ${this.configuration.prefix}`,
);
}
}
}

View File

@@ -0,0 +1 @@
export * from "./S3.ts";