diff --git a/.changeset/ninety-hats-behave.md b/.changeset/ninety-hats-behave.md new file mode 100644 index 000000000..c5166bfbf --- /dev/null +++ b/.changeset/ninety-hats-behave.md @@ -0,0 +1,7 @@ +--- +'@powersync/diagnostics-app': minor +'@powersync/common': minor +'@powersync/web': minor +--- + +Remove lodash dependency. diff --git a/packages/common/package.json b/packages/common/package.json index fb0250c61..4a28119e4 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -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", @@ -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", diff --git a/packages/common/src/client/AbstractPowerSyncDatabase.ts b/packages/common/src/client/AbstractPowerSyncDatabase.ts index 9cbf37f2e..d6cffe539 100644 --- a/packages/common/src/client/AbstractPowerSyncDatabase.ts +++ b/packages/common/src/client/AbstractPowerSyncDatabase.ts @@ -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, @@ -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'; @@ -898,14 +898,13 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver 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({ diff --git a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts index 73cce7d10..4baa5a47e 100644 --- a/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +++ b/packages/common/src/client/sync/stream/AbstractStreamingSyncImplementation.ts @@ -1,5 +1,3 @@ -import throttle from 'lodash/throttle'; - import Logger, { ILogger } from 'js-logger'; import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus'; @@ -18,6 +16,7 @@ import { isStreamingSyncCheckpointDiff, isStreamingSyncData } from './streaming-sync-types'; +import { throttleLeadingTrailing } from '../../../utils/throttle'; export enum LockType { CRUD = 'crud', @@ -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() {} diff --git a/packages/common/src/utils/throttle.ts b/packages/common/src/utils/throttle.ts new file mode 100644 index 000000000..d869f8c36 --- /dev/null +++ b/packages/common/src/utils/throttle.ts @@ -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 | 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 | 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); + } + }; +} diff --git a/packages/web/package.json b/packages/web/package.json index 1b6691f35..3aa957b3d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -42,8 +42,7 @@ "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", @@ -51,7 +50,6 @@ "@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", diff --git a/packages/web/tests/stream.test.ts b/packages/web/tests/stream.test.ts index e5041f764..f8119d419 100644 --- a/packages/web/tests/stream.test.ts +++ b/packages/web/tests/stream.test.ts @@ -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((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. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a604173cd..153339557 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1307,9 +1307,6 @@ importers: '@rollup/plugin-terser': specifier: ^0.4.4 version: 0.4.4(rollup@4.14.3) - '@types/lodash': - specifier: ^4.14.197 - version: 4.17.7 '@types/node': specifier: ^20.5.9 version: 20.16.3 @@ -1334,9 +1331,6 @@ importers: event-iterator: specifier: ^2.0.0 version: 2.0.0 - lodash: - specifier: ^4.17.21 - version: 4.17.21 rollup: specifier: 4.14.3 version: 4.14.3 @@ -1533,9 +1527,6 @@ importers: js-logger: specifier: ^1.6.1 version: 1.6.1 - lodash: - specifier: ^4.17.21 - version: 4.17.21 devDependencies: '@journeyapps/wa-sqlite': specifier: ^0.3.0 @@ -1552,9 +1543,6 @@ importers: '@rollup/plugin-node-resolve': specifier: 15.2.3 version: 15.2.3(rollup@4.14.3) - '@types/lodash': - specifier: ^4.14.200 - version: 4.17.7 '@types/uuid': specifier: ^9.0.6 version: 9.0.8 @@ -1609,9 +1597,6 @@ importers: js-logger: specifier: ^1.6.1 version: 1.6.1 - lodash: - specifier: ^4.17.21 - version: 4.17.21 react: specifier: ^18.2.0 version: 18.2.0 @@ -1625,9 +1610,6 @@ importers: '@swc/core': specifier: ~1.6.0 version: 1.6.13(@swc/helpers@0.5.5) - '@types/lodash': - specifier: ^4.14.202 - version: 4.17.7 '@types/node': specifier: ^20.11.25 version: 20.16.3 diff --git a/tools/diagnostics-app/package.json b/tools/diagnostics-app/package.json index 4e219487a..9a906dedc 100644 --- a/tools/diagnostics-app/package.json +++ b/tools/diagnostics-app/package.json @@ -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",