WIP: Smart create call #184

Draft
schmelczer wants to merge 46 commits from asch/smart-create into main
17 changed files with 47 additions and 72 deletions
Showing only changes of commit 0d7d36e971 - Show all commits

View file

@ -6,7 +6,7 @@ on:
pull_request:
branches: ["main"]
schedule:
- cron: '0 * * * *'
- cron: "0 * * * *"
workflow_dispatch:
concurrency:

View file

@ -27,4 +27,4 @@
"webpack-merge": "^6.0.1",
"@sentry/browser": "^10.30.0"
}
}
}

View file

@ -14,14 +14,15 @@ export class ServerConfig {
private response: Promise<PingResponse> | undefined;
private config: ServerConfigData | undefined;
public constructor(private readonly syncService: SyncService) { }
public constructor(private readonly syncService: SyncService) {}
private static validateConfig(config: ServerConfigData): void {
if (config.supportedApiVersion !== SUPPORTED_API_VERSION) {
const shouldUpgradeClient =
config.supportedApiVersion > SUPPORTED_API_VERSION;
throw new ServerVersionMismatchError(
`Unsupported API version: ${config.supportedApiVersion}. Consider upgrading the ${shouldUpgradeClient ? "client" : "sync-server"
`Unsupported API version: ${config.supportedApiVersion}. Consider upgrading the ${
shouldUpgradeClient ? "client" : "sync-server"
} to ensure compatibility`
);
}

View file

@ -73,7 +73,7 @@ export class SyncService {
relativePath: RelativePath;
contentBytes: Uint8Array;
forceMerge?: boolean;
}): Promise<DocumentVersionWithoutContent> {
}): Promise<DocumentUpdateResponse> {
return this.retryForever(async () => {
const formData = new FormData();
@ -105,8 +105,8 @@ export class SyncService {
);
}
const result: DocumentVersionWithoutContent =
(await response.json()) as DocumentVersionWithoutContent; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
const result: DocumentUpdateResponse =
(await response.json()) as DocumentUpdateResponse; // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
this.logger.debug(`Created document ${JSON.stringify(result)}`);

View file

@ -1,13 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface CreateDocumentVersion {
/**
* The client can decide the document id (if it wishes to) in order
* to help with syncing. If the client does not provide a document id,
* the server will generate one. If the client provides a document id
* it must not already exist in the database.
*/
document_id: string | null;
relative_path: string;
force_merge: boolean | null;
content: number[];
}

View file

@ -36,7 +36,7 @@ export class WebSocketManager {
private readonly logger: Logger,
private readonly settings: Settings,
private readonly webSocketFactoryImplementation: typeof globalThis.WebSocket = WebSocket
) { }
) {}
public get isWebSocketConnected(): boolean {
return (

View file

@ -56,7 +56,7 @@ export class SyncClient {
database: Partial<StoredDatabase>;
}>
>
) { }
) {}
public get documentCount(): number {
return this.database.length;
@ -205,7 +205,6 @@ export class SyncClient {
logger,
database,
settings,
syncService,
webSocketManager,
fileOperations,
unrestrictedSyncer

View file

@ -4,7 +4,6 @@ import type {
DocumentRecord,
RelativePath
} from "../persistence/database";
import type { SyncService } from "../services/sync-service";
import type { Logger } from "../tracing/logger";
import PQueue from "p-queue";
import { hash } from "../utils/hash";
@ -41,7 +40,6 @@ export class Syncer {
private readonly logger: Logger,
private readonly database: Database,
private readonly settings: Settings,
private readonly syncService: SyncService,
private readonly webSocketManager: WebSocketManager,
private readonly operations: FileOperations,
private readonly internalSyncer: UnrestrictedSyncer
@ -487,8 +485,5 @@ export class Syncer {
})
);
this.database.setHasInitialSyncCompleted(true);
}
}

View file

@ -1,5 +1,3 @@
export function createClientId(): string {
// @ts-expect-error, injected by webpack
const packageVersion = __CURRENT_VERSION__; // eslint-disable-line
@ -8,8 +6,8 @@ export function createClientId(): string {
typeof navigator !== "undefined"
? navigator.platform // eslint-disable-line @typescript-eslint/no-deprecated
: typeof process !== "undefined"
? process.platform
: "unknown";
? process.platform
: "unknown";
return `vault-link/${packageVersion} (${Math.round(Math.random() * 1e10)}; ${platform})`;
}

View file

@ -252,7 +252,7 @@ describe("reset", () => {
await sleep(1);
const secondPromise = locks.withLock(testPath, async () => "second");
void secondPromise.catch(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
void secondPromise.catch(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
locks.reset();
@ -273,7 +273,7 @@ describe("reset", () => {
await sleep(1);
const secondPromise = locks.withLock(testPath, async () => "second");
void secondPromise.catch(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
void secondPromise.catch(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
locks.reset();

View file

@ -18,7 +18,7 @@ export class Locks<T> {
[() => unknown, (err: unknown) => unknown][]
>();
public constructor(private readonly logger?: Logger) { }
public constructor(private readonly logger?: Logger) {}
/**
* Executes a function while holding exclusive locks on one or more keys.

View file

@ -14,13 +14,7 @@ export class MockClient implements FileSystemOperations {
protected data: Partial<{
settings: Partial<SyncSettings>;
database: Partial<StoredDatabase>;
}> = {
database: {
// Assume all clients start at the same time so there's no need to fetch
// any shared state.
hasInitialSyncCompleted: true
}
};
}> = {};
public constructor(
initialSettings: Partial<SyncSettings>,
@ -108,13 +102,13 @@ export class MockClient implements FileSystemOperations {
.map((part) => part.trim());
const newParts = newContent.split(" ").map((part) => part.trim());
existingParts.forEach((part) =>
// all changes should be additive
{
assert(
newParts.includes(part),
`Part ${part} not found in new content: ${newContent}`
);
}
// all changes should be additive
{
assert(
newParts.includes(part),
`Part ${part} not found in new content: ${newContent}`
);
}
);
}

View file

@ -30,8 +30,11 @@ fi
which cargo-machete || cargo install cargo-machete
cargo machete --with-metadata
cd ..
scripts/update-api-types.sh # this will dirty up the git state if not up-to-date
echo "Running checks in frontend"
cd ../frontend
cd frontend
if [[ "$FIX_MODE" == true ]]; then
npm install

View file

@ -25,15 +25,6 @@ npm run build
../scripts/utils/wait-for-server.sh
cd ..
scripts/update-api-types.sh
if [[ $(git status --porcelain) ]]; then
git status --porcelain
echo "Failing CI because the working directory is not clean after generating api types"
exit 1
fi
cd frontend
pids=()
for i in $(seq 1 $process_count); do
# Create a named pipe for this process

View file

@ -9,24 +9,24 @@ server:
max_clients_per_vault: 256
response_timeout: 30m
mergeable_file_extensions:
- md
- txt
- md
- txt
users:
user_configs:
- name: admin
token: test-token-change-me
vault_access:
type: allow_access_to_all
- name: other-admin
token: test-token-change-me2
vault_access:
type: allow_access_to_all
- name: test
token: other-test-token
vault_access:
type: allow_list
allowed:
- default
- name: admin
token: test-token-change-me
vault_access:
type: allow_access_to_all
- name: other-admin
token: test-token-change-me2
vault_access:
type: allow_access_to_all
- name: test
token: other-test-token
vault_access:
type: allow_list
allowed:
- default
logging:
log_directory: logs
log_rotation: 7days

View file

@ -11,7 +11,7 @@ use crate::app_state::database::models::VaultUpdateId;
pub struct CreateDocumentVersion {
pub relative_path: String,
// whether to merge with existing document at the same path if it exists
// whether to merge with existing document at the same path if it already exists
pub force_merge: Option<bool>,
#[ts(as = "Vec<u8>")]

View file

@ -246,8 +246,6 @@ pub async fn merge_with_stored_version(
content.clone()
};
let is_different_from_request_content = merged_content != content;
// We can only update the relative path if we're the first one to do so
let new_relative_path = if parent_document.relative_path == latest_version.relative_path
&& latest_version.relative_path != sanitized_relative_path
@ -278,6 +276,8 @@ pub async fn merge_with_stored_version(
.await
.map_err(server_error)?;
let is_different_from_request_content = merged_content != content;
let new_version = StoredDocumentVersion {
document_id: parent_document.document_id,
vault_update_id: last_update_id + 1,