mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(server): rename @affine/storage to @affine/server-native (#6682)
- Close https://github.com/toeverything/AFFiNE/issues/6680
This commit is contained in:
24
packages/backend/native/Cargo.toml
Normal file
24
packages/backend/native/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "affine_server_native"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
napi = { version = "2", default-features = false, features = [
|
||||
"napi5",
|
||||
"async",
|
||||
] }
|
||||
napi-derive = { version = "2", features = ["type-def"] }
|
||||
rand = "0.8"
|
||||
sha3 = "0.10"
|
||||
y-octo = { git = "https://github.com/y-crdt/y-octo.git", branch = "main" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = "1"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2"
|
||||
165
packages/backend/native/__tests__/storage.spec.js
Normal file
165
packages/backend/native/__tests__/storage.spec.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import assert from 'node:assert';
|
||||
import { beforeEach, describe, test } from 'node:test';
|
||||
|
||||
import { encoding } from 'lib0';
|
||||
import { applyUpdate, Doc } from 'yjs';
|
||||
|
||||
import { Storage } from '../index.js';
|
||||
|
||||
// update binary by y.doc.text('content').insert('hello world')
|
||||
// prettier-ignore
|
||||
let init = Buffer.from([
|
||||
1,
|
||||
1,
|
||||
160,
|
||||
238,
|
||||
169,
|
||||
240,
|
||||
10,
|
||||
0,
|
||||
4,
|
||||
1,
|
||||
7,
|
||||
99,
|
||||
111,
|
||||
110,
|
||||
116,
|
||||
101,
|
||||
110,
|
||||
116,
|
||||
11,
|
||||
104,
|
||||
101,
|
||||
108,
|
||||
108,
|
||||
111,
|
||||
32,
|
||||
119,
|
||||
111,
|
||||
114,
|
||||
108,
|
||||
100,
|
||||
0])
|
||||
describe('Test jwst storage binding', () => {
|
||||
/** @type { Storage } */
|
||||
let storage;
|
||||
beforeEach(async () => {
|
||||
storage = await Storage.connect('sqlite::memory:', true);
|
||||
});
|
||||
|
||||
test('should be able to create workspace', async () => {
|
||||
const workspace = await storage.createWorkspace('test-workspace', init);
|
||||
|
||||
assert(workspace.id === 'test-workspace');
|
||||
assert.deepEqual(init, await storage.load(workspace.doc.guid));
|
||||
});
|
||||
|
||||
test('should not create workspace with same id', async () => {
|
||||
await storage.createWorkspace('test-workspace', init);
|
||||
await assert.rejects(
|
||||
storage.createWorkspace('test-workspace', init),
|
||||
/Workspace [\w-]+ already exists/
|
||||
);
|
||||
});
|
||||
|
||||
test('should be able to delete workspace', async () => {
|
||||
const workspace = await storage.createWorkspace('test-workspace', init);
|
||||
|
||||
await storage.deleteWorkspace(workspace.id);
|
||||
|
||||
await assert.rejects(
|
||||
storage.load(workspace.doc.guid),
|
||||
/Doc [\w-]+ not exists/
|
||||
);
|
||||
});
|
||||
|
||||
test('should be able to sync update', async () => {
|
||||
const workspace = await storage.createWorkspace('test-workspace', init);
|
||||
|
||||
const update = await storage.load(workspace.doc.guid);
|
||||
assert(update !== null);
|
||||
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, update);
|
||||
|
||||
let text = doc.getText('content');
|
||||
assert.equal(text.toJSON(), 'hello world');
|
||||
|
||||
const updates = [];
|
||||
doc.on('update', async (/** @type { UInt8Array } */ update) => {
|
||||
updates.push(Buffer.from(update));
|
||||
});
|
||||
|
||||
text.insert(5, ' my');
|
||||
text.insert(14, '!');
|
||||
|
||||
for (const update of updates) {
|
||||
await storage.sync(workspace.id, workspace.doc.guid, update);
|
||||
}
|
||||
|
||||
const update2 = await storage.load(workspace.doc.guid);
|
||||
const doc2 = new Doc();
|
||||
applyUpdate(doc2, update2);
|
||||
|
||||
text = doc2.getText('content');
|
||||
assert.equal(text.toJSON(), 'hello my world!');
|
||||
});
|
||||
|
||||
test('should be able to sync update with guid encoded', async () => {
|
||||
const workspace = await storage.createWorkspace('test-workspace', init);
|
||||
|
||||
const update = await storage.load(workspace.doc.guid);
|
||||
assert(update !== null);
|
||||
|
||||
const doc = new Doc();
|
||||
applyUpdate(doc, update);
|
||||
|
||||
let text = doc.getText('content');
|
||||
assert.equal(text.toJSON(), 'hello world');
|
||||
|
||||
const updates = [];
|
||||
doc.on('update', async (/** @type { UInt8Array } */ update) => {
|
||||
const prefix = encoding.encode(encoder => {
|
||||
encoding.writeVarString(encoder, workspace.doc.guid);
|
||||
});
|
||||
|
||||
updates.push(Buffer.concat([prefix, update]));
|
||||
});
|
||||
|
||||
text.insert(5, ' my');
|
||||
text.insert(14, '!');
|
||||
|
||||
for (const update of updates) {
|
||||
await storage.syncWithGuid(workspace.id, update);
|
||||
}
|
||||
|
||||
const update2 = await storage.load(workspace.doc.guid);
|
||||
const doc2 = new Doc();
|
||||
applyUpdate(doc2, update2);
|
||||
|
||||
text = doc2.getText('content');
|
||||
assert.equal(text.toJSON(), 'hello my world!');
|
||||
});
|
||||
|
||||
test('should be able to store blob', async () => {
|
||||
let workspace = await storage.createWorkspace('test-workspace');
|
||||
await storage.sync(workspace.id, workspace.doc.guid, init);
|
||||
const blobId = await storage.uploadBlob(workspace.id, Buffer.from([1]));
|
||||
|
||||
assert(blobId !== null);
|
||||
|
||||
let list = await storage.listBlobs(workspace.id);
|
||||
assert.deepEqual(list, [blobId]);
|
||||
|
||||
let blob = await storage.getBlob(workspace.id, blobId);
|
||||
assert.deepEqual(blob.data, Buffer.from([1]));
|
||||
assert.strictEqual(blob.size, 1);
|
||||
assert.equal(blob.contentType, 'application/octet-stream');
|
||||
|
||||
await storage.uploadBlob(workspace.id, Buffer.from([1, 2, 3, 4, 5]));
|
||||
|
||||
const spaceTaken = await storage.blobsSize(workspace.id);
|
||||
|
||||
assert.equal(spaceTaken, 6);
|
||||
});
|
||||
});
|
||||
3
packages/backend/native/build.rs
Normal file
3
packages/backend/native/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
13
packages/backend/native/index.d.ts
vendored
Normal file
13
packages/backend/native/index.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Merge updates in form like `Y.applyUpdate(doc, update)` way and return the
|
||||
* result binary.
|
||||
*/
|
||||
export function mergeUpdatesInApplyWay(updates: Array<Buffer>): Buffer
|
||||
|
||||
export function mintChallengeResponse(resource: string, bits?: number | undefined | null): Promise<string>
|
||||
|
||||
export function verifyChallengeResponse(response: string, bits: number, resource: string): Promise<boolean>
|
||||
|
||||
11
packages/backend/native/index.js
Normal file
11
packages/backend/native/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/** @type {import('.')} */
|
||||
const binding = require('./storage.node');
|
||||
|
||||
export const Storage = binding.Storage;
|
||||
export const mergeUpdatesInApplyWay = binding.mergeUpdatesInApplyWay;
|
||||
export const verifyChallengeResponse = binding.verifyChallengeResponse;
|
||||
export const mintChallengeResponse = binding.mintChallengeResponse;
|
||||
41
packages/backend/native/package.json
Normal file
41
packages/backend/native/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@affine/server-native",
|
||||
"version": "0.14.0",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0 < 11 || >= 11.8.0"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"types": "index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./server-native.node",
|
||||
"import": "./index.js",
|
||||
"types": "./index.d.ts"
|
||||
}
|
||||
},
|
||||
"napi": {
|
||||
"binaryName": "server-native",
|
||||
"targets": [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-linux-gnu"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test ./__tests__/**/*.spec.js",
|
||||
"build": "napi build --release --strip --no-const-enum",
|
||||
"build:debug": "napi build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "3.0.0-alpha.46",
|
||||
"lib0": "^0.2.93",
|
||||
"nx": "^18.2.4",
|
||||
"nx-cloud": "^18.0.0",
|
||||
"yjs": "^13.6.14"
|
||||
}
|
||||
}
|
||||
23
packages/backend/native/project.json
Normal file
23
packages/backend/native/project.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@affine/server-native",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"root": "packages/backend/native",
|
||||
"sourceRoot": "packages/backend/native/src",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"dependsOn": ["^build"],
|
||||
"options": {
|
||||
"script": "build"
|
||||
},
|
||||
"inputs": [
|
||||
{ "runtime": "rustc --version" },
|
||||
{ "runtime": "node -v" },
|
||||
{ "runtime": "clang --version" },
|
||||
{ "runtime": "cargo tree" }
|
||||
],
|
||||
"outputs": ["{projectRoot}/*.node", "{workspaceRoot}/*.node"]
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/backend/native/src/hashcash.rs
Symbolic link
1
packages/backend/native/src/hashcash.rs
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../frontend/native/src/hashcash.rs
|
||||
44
packages/backend/native/src/lib.rs
Normal file
44
packages/backend/native/src/lib.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod hashcash;
|
||||
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
use napi::{bindgen_prelude::*, Error, Result, Status};
|
||||
use y_octo::Doc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
fn map_err_inner<T, E: Display + Debug>(v: std::result::Result<T, E>, status: Status) -> Result<T> {
|
||||
match v {
|
||||
Ok(val) => Ok(val),
|
||||
Err(e) => {
|
||||
dbg!(&e);
|
||||
Err(Error::new(status, e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! map_err {
|
||||
($val: expr) => {
|
||||
map_err_inner($val, Status::GenericFailure)
|
||||
};
|
||||
($val: expr, $stauts: ident) => {
|
||||
map_err_inner($val, $stauts)
|
||||
};
|
||||
}
|
||||
|
||||
/// Merge updates in form like `Y.applyUpdate(doc, update)` way and return the
|
||||
/// result binary.
|
||||
#[napi(catch_unwind)]
|
||||
pub fn merge_updates_in_apply_way(updates: Vec<Buffer>) -> Result<Buffer> {
|
||||
let mut doc = Doc::default();
|
||||
for update in updates {
|
||||
map_err!(doc.apply_update_from_binary_v1(update.as_ref()))?;
|
||||
}
|
||||
|
||||
let buf = map_err!(doc.encode_update_v1())?;
|
||||
|
||||
Ok(buf.into())
|
||||
}
|
||||
9
packages/backend/native/tsconfig.json
Normal file
9
packages/backend/native/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["index.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user