Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/ninety-hats-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@powersync/diagnostics-app': minor
'@powersync/common': minor
'@powersync/web': minor
---

Remove lodash dependency.
2 changes: 0 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@types/lodash": "^4.14.197",
"@types/node": "^20.5.9",
"@types/uuid": "^9.0.1",
"async-mutex": "^0.4.0",
Expand All @@ -45,7 +44,6 @@
"can-ndjson-stream": "^1.0.2",
"cross-fetch": "^4.0.0",
"event-iterator": "^2.0.0",
"lodash": "^4.17.21",
"rollup": "4.14.3",
"rsocket-core": "1.0.0-alpha.3",
"rsocket-websocket-client": "1.0.0-alpha.3",
Expand Down
7 changes: 3 additions & 4 deletions packages/common/src/client/AbstractPowerSyncDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Mutex } from 'async-mutex';
import { EventIterator } from 'event-iterator';
import Logger, { ILogger } from 'js-logger';
import throttle from 'lodash/throttle';
import {
BatchedUpdateNotification,
DBAdapter,
Expand All @@ -16,6 +15,7 @@ import { Schema } from '../db/schema/Schema';
import { BaseObserver } from '../utils/BaseObserver';
import { ControlledExecutor } from '../utils/ControlledExecutor';
import { mutexRunExclusive } from '../utils/mutex';
import { throttleTrailing } from '../utils/throttle.js';
import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory';
import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector';
import { BucketStorageAdapter, PSInternalTable } from './sync/bucket/BucketStorageAdapter';
Expand Down Expand Up @@ -898,14 +898,13 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
await onChange(e);
});

const flushTableUpdates = throttle(
const flushTableUpdates = throttleTrailing(
() =>
this.handleTableChanges(changedTables, watchedTables, (intersection) => {
if (resolvedOptions?.signal?.aborted) return;
executor.schedule({ changedTables: intersection });
}),
throttleMs,
{ leading: false, trailing: true }
throttleMs
);

const dispose = this.database.registerListener({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import throttle from 'lodash/throttle';

import Logger, { ILogger } from 'js-logger';

import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus';
Expand All @@ -18,6 +16,7 @@ import {
isStreamingSyncCheckpointDiff,
isStreamingSyncData
} from './streaming-sync-types';
import { throttleLeadingTrailing } from '../../../utils/throttle';

export enum LockType {
CRUD = 'crud',
Expand Down Expand Up @@ -142,16 +141,12 @@ export abstract class AbstractStreamingSyncImplementation
});
this.abortController = null;

this.triggerCrudUpload = throttle(
() => {
if (!this.syncStatus.connected || this.syncStatus.dataFlowStatus.uploading) {
return;
}
this._uploadAllCrud();
},
this.options.crudUploadThrottleMs,
{ trailing: true }
);
this.triggerCrudUpload = throttleLeadingTrailing(() => {
if (!this.syncStatus.connected || this.syncStatus.dataFlowStatus.uploading) {
return;
}
this._uploadAllCrud();
}, this.options.crudUploadThrottleMs!);
}

async waitForReady() {}
Expand Down
50 changes: 50 additions & 0 deletions packages/common/src/utils/throttle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Throttle a function to be called at most once every "wait" milliseconds,
* on the trailing edge.
*
* Roughly equivalent to lodash/throttle with {leading: false, trailing: true}
*/
export function throttleTrailing(func: () => void, wait: number) {
let timeoutId: ReturnType<typeof setTimeout> | null = null;

const later = () => {
func();
timeoutId = null;
};

return function () {
if (timeoutId == null) {
timeoutId = setTimeout(later, wait);
}
};
}

/**
* Throttle a function to be called at most once every "wait" milliseconds,
* on the leading and trailing edge.
*
* Roughly equivalent to lodash/throttle with {leading: true, trailing: true}
*/
export function throttleLeadingTrailing(func: () => void, wait: number) {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let lastCallTime: number = 0;

const invokeFunction = () => {
func();
lastCallTime = Date.now();
timeoutId = null;
};

return function () {
const now = Date.now();
const timeToWait = wait - (now - lastCallTime);

if (timeToWait <= 0) {
// Leading edge: Call the function immediately if enough time has passed
invokeFunction();
} else if (!timeoutId) {
// Set a timeout for the trailing edge if not already set
timeoutId = setTimeout(invokeFunction, timeToWait);
}
};
}
4 changes: 1 addition & 3 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,14 @@
"async-mutex": "^0.4.0",
"bson": "^6.6.0",
"comlink": "^4.4.1",
"js-logger": "^1.6.1",
"lodash": "^4.17.21"
"js-logger": "^1.6.1"
},
"devDependencies": {
"@journeyapps/wa-sqlite": "^0.3.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-inject": "^5.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "15.2.3",
"@types/lodash": "^4.14.200",
"@types/uuid": "^9.0.6",
"@vitest/browser": "^1.3.1",
"p-defer": "^4.0.1",
Expand Down
24 changes: 2 additions & 22 deletions packages/web/tests/stream.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
import _ from 'lodash';
import { Schema, TableV2, column } from '@powersync/common';
import Logger from 'js-logger';
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { v4 as uuid } from 'uuid';
import { AbstractPowerSyncDatabase, Schema, SyncStatusOptions, TableV2, column } from '@powersync/common';
import { beforeAll, describe, expect, it, vi } from 'vitest';
import { MockRemote, MockStreamOpenFactory, TestConnector } from './utils/MockStreamOpenFactory';

const UPLOAD_TIMEOUT_MS = 3000;

export async function waitForConnectionStatus(
db: AbstractPowerSyncDatabase,
statusCheck: SyncStatusOptions = { connected: true }
) {
await new Promise<void>((resolve) => {
if (db.connected) {
resolve();
}
const l = db.registerListener({
statusUpdated: (status) => {
if (_.every(statusCheck, (value, key) => _.isEqual(status[key as keyof SyncStatusOptions], value))) {
resolve();
l?.();
}
}
});
});
}

export async function generateConnectedDatabase({ useWebWorker } = { useWebWorker: true }) {
/**
* Very basic implementation of a listener pattern.
Expand Down
18 changes: 0 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions tools/diagnostics-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
"@mui/material": "^5.15.12",
"@mui/x-data-grid": "^6.19.6",
"js-logger": "^1.6.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3"
},
"devDependencies": {
"@swc/core": "~1.6.0",
"@types/lodash": "^4.14.202",
"@types/node": "^20.11.25",
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
Expand Down
Loading