feat(server): lightweight s3 client (#14348)

#### PR Dependency Tree


* **PR #14348** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a dedicated S3-compatible client package and expanded
S3-compatible storage config (endpoint, region, forcePathStyle,
requestTimeoutMs, minPartSize, presign options, sessionToken).
* Document sync now broadcasts batched/compressed doc updates for more
efficient real-time syncing.

* **Tests**
* New unit and benchmark tests for base64 utilities and S3 multipart
listing; updated storage-related tests to match new formats.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-01 21:54:39 +08:00
committed by GitHub
parent 059d3aa04a
commit f1a6e409cb
37 changed files with 1539 additions and 1712 deletions
@@ -0,0 +1,23 @@
import { bench, describe } from 'vitest';
import { base64ToUint8Array, uint8ArrayToBase64 } from '../impls/cloud/socket';
const data = new Uint8Array(1024 * 256);
for (let i = 0; i < data.length; i++) {
data[i] = i % 251;
}
let encoded = '';
await uint8ArrayToBase64(data).then(result => {
encoded = result;
});
describe('base64 helpers', () => {
bench('encode Uint8Array to base64', async () => {
await uint8ArrayToBase64(data);
});
bench('decode base64 to Uint8Array', () => {
base64ToUint8Array(encoded);
});
});
@@ -0,0 +1,27 @@
import { describe, expect, test } from 'vitest';
import { base64ToUint8Array, uint8ArrayToBase64 } from '../impls/cloud/socket';
function makeSample(size: number) {
const data = new Uint8Array(size);
for (let i = 0; i < size; i++) {
data[i] = i % 251;
}
return data;
}
describe('base64 helpers', () => {
test('roundtrip preserves data', async () => {
const input = makeSample(1024);
const encoded = await uint8ArrayToBase64(input);
const decoded = base64ToUint8Array(encoded);
expect(decoded).toEqual(input);
});
test('handles large payloads', async () => {
const input = makeSample(256 * 1024);
const encoded = await uint8ArrayToBase64(input);
const decoded = base64ToUint8Array(encoded);
expect(decoded).toEqual(input);
});
});
@@ -0,0 +1,41 @@
import { describe, expect, test } from 'vitest';
import { CloudDocStorage } from '../impls/cloud/doc';
const base64UpdateA = 'AQID';
const base64UpdateB = 'BAUG';
describe('CloudDocStorage broadcast updates', () => {
test('emits updates from batch payload', () => {
const storage = new CloudDocStorage({
id: 'space-1',
serverBaseUrl: 'http://localhost',
isSelfHosted: true,
type: 'workspace',
readonlyMode: true,
});
(storage as any).connection.idConverter = {
oldIdToNewId: (id: string) => id,
newIdToOldId: (id: string) => id,
};
const received: Uint8Array[] = [];
storage.subscribeDocUpdate(update => {
received.push(update.bin);
});
storage.onServerUpdates({
spaceType: 'workspace',
spaceId: 'space-1',
docId: 'doc-1',
updates: [base64UpdateA, base64UpdateB],
timestamp: Date.now(),
});
expect(received).toEqual([
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
]);
});
});