WIP: Smart create call #184
17 changed files with 47 additions and 72 deletions
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
|||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
- cron: "0 * * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
|
|
|||
|
|
@ -27,4 +27,4 @@
|
|||
"webpack-merge": "^6.0.1",
|
||||
"@sentry/browser": "^10.30.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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})`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>")]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue