diff --git a/.changeset/funny-pianos-allow.md b/.changeset/funny-pianos-allow.md deleted file mode 100644 index 3df90e3bc..000000000 --- a/.changeset/funny-pianos-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@powersync/service-core': patch ---- - -Fix "operation exceeded time limit" error diff --git a/.changeset/green-books-shout.md b/.changeset/green-books-shout.md deleted file mode 100644 index 1b2ef8139..000000000 --- a/.changeset/green-books-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@powersync/service-core': patch ---- - -Fix storageStats error in metrics endpoint when collections don't exist. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3705cd600..e8a6f47ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ on: jobs: test-service-container-build: - name: Build and Test Powersync Service + name: Build and Test PowerSync Service runs-on: ubuntu-latest steps: - name: Checkout diff --git a/DEVELOP.md b/DEVELOP.md index 23eb51d4d..3b88419e6 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -18,7 +18,7 @@ pnpm build ## Dependent Services -The PowerSync service requires Postgres and MongoDB server connections. These configuration details can be specified in a `powersync.yaml` (or JSON) configuration file. +The PowerSync Service requires Postgres and MongoDB server connections. These configuration details can be specified in a `powersync.yaml` (or JSON) configuration file. See the [self-hosting demo](https://github.com/powersync-ja/self-host-demo) for demos of starting these services. @@ -36,7 +36,7 @@ One method to obtain access is to add the following to `/etc/hosts` (on Unix-lik 127.0.0.1 mongo ``` -This will start all the services defined in the Self hosting demo except for the PowerSync service - which will be started from this repository. +This will start all the services defined in the self-hosting demo except for the PowerSync Service - which will be started from this repository. ## Local Configuration diff --git a/README.md b/README.md index 8b994b142..1e9ac4fbb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres or MongoDB on the server-side (MySQL coming soon)._ +_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB or MySQL on the server-side._ # PowerSync Service diff --git a/modules/module-postgres/src/types/types.ts b/modules/module-postgres/src/types/types.ts index 9b5be2d2b..858a30f2a 100644 --- a/modules/module-postgres/src/types/types.ts +++ b/modules/module-postgres/src/types/types.ts @@ -142,11 +142,10 @@ export function validatePort(port: string | number): number { if (typeof port == 'string') { port = parseInt(port); } - if (port >= 1024 && port <= 49151) { - return port; - } else { + if (port < 1024) { throw new Error(`Port ${port} not supported`); - } + } + return port; } /** diff --git a/packages/service-core/CHANGELOG.md b/packages/service-core/CHANGELOG.md index e76e019d4..4a718373f 100644 --- a/packages/service-core/CHANGELOG.md +++ b/packages/service-core/CHANGELOG.md @@ -1,5 +1,20 @@ # @powersync/service-core +## 0.8.7 + +### Patch Changes + +- 6b72e6c: Improved Postgres connection port restrictions. Connections are now supported on ports >= 1024. + +## 0.8.6 + +### Patch Changes + +- 2d3bb6a: Fix "operation exceeded time limit" error +- 17a6db0: Fix storageStats error in metrics endpoint when collections don't exist. +- Updated dependencies [0f90b02] + - @powersync/service-sync-rules@0.20.0 + ## 0.8.5 ### Patch Changes diff --git a/packages/service-core/package.json b/packages/service-core/package.json index b2fe5b340..865974eab 100644 --- a/packages/service-core/package.json +++ b/packages/service-core/package.json @@ -5,7 +5,7 @@ "publishConfig": { "access": "public" }, - "version": "0.8.5", + "version": "0.8.7", "main": "dist/index.js", "license": "FSL-1.1-Apache-2.0", "type": "module", @@ -13,7 +13,7 @@ "build": "tsc -b", "build:tests": "tsc -b test/tsconfig.json", "test": "vitest", - "clean": "rm -rf ./lib && tsc -b --clean" + "clean": "rm -rf ./dist && tsc -b --clean" }, "dependencies": { "@js-sdsl/ordered-set": "^4.4.2", diff --git a/packages/sync-rules/CHANGELOG.md b/packages/sync-rules/CHANGELOG.md index 099bf11db..d74892def 100644 --- a/packages/sync-rules/CHANGELOG.md +++ b/packages/sync-rules/CHANGELOG.md @@ -1,5 +1,11 @@ # @powersync/service-sync-rules +## 0.20.0 + +### Minor Changes + +- 0f90b02: Support substring and json_keys functions in sync rules + ## 0.19.0 ### Minor Changes diff --git a/packages/sync-rules/package.json b/packages/sync-rules/package.json index 727188bb6..085f36f7b 100644 --- a/packages/sync-rules/package.json +++ b/packages/sync-rules/package.json @@ -1,7 +1,7 @@ { "name": "@powersync/service-sync-rules", "repository": "https://github.com/powersync-ja/powersync-service", - "version": "0.19.0", + "version": "0.20.0", "main": "dist/index.js", "types": "dist/index.d.ts", "license": "FSL-1.1-Apache-2.0", diff --git a/packages/sync-rules/src/sql_functions.ts b/packages/sync-rules/src/sql_functions.ts index ce419d49a..fd6f71b91 100644 --- a/packages/sync-rules/src/sql_functions.ts +++ b/packages/sync-rules/src/sql_functions.ts @@ -82,6 +82,61 @@ const lower: DocumentedSqlFunction = { detail: 'Convert text to lower case' }; +const substring: DocumentedSqlFunction = { + debugName: 'substring', + call(value: SqliteValue, start: SqliteValue, length?: SqliteValue) { + const text = castAsText(value); + if (text == null) { + return null; + } + const startIndex = cast(start, 'integer') as bigint | null; + if (startIndex == null) { + return null; + } + if (length === null) { + // Different from undefined in this case, to match SQLite behavior + return null; + } + const castLength = cast(length ?? null, 'integer') as bigint | null; + let realLength: number; + if (castLength == null) { + // undefined (not specified) + realLength = text.length + 1; // +1 to account for the start = 0 special case + } else { + realLength = Number(castLength); + } + + let realStart = 0; + if (startIndex < 0n) { + realStart = Math.max(0, text.length + Number(startIndex)); + } else if (startIndex == 0n) { + // Weird special case + realStart = 0; + realLength -= 1; + } else { + realStart = Number(startIndex) - 1; + } + + if (realLength < 0) { + // Negative length means we return that many characters _before_ + // the start index. + return text.substring(realStart + realLength, realStart); + } + + return text.substring(realStart, realStart + realLength); + }, + parameters: [ + { name: 'value', type: ExpressionType.TEXT, optional: false }, + { name: 'start', type: ExpressionType.INTEGER, optional: false }, + { name: 'length', type: ExpressionType.INTEGER, optional: true } + ], + getReturnType(args) { + return ExpressionType.TEXT; + }, + detail: 'Compute a substring', + documentation: 'The start index starts at 1. If no length is specified, the remainder of the string is returned.' +}; + const hex: DocumentedSqlFunction = { debugName: 'hex', call(value: SqliteValue) { @@ -235,6 +290,32 @@ const json_valid: DocumentedSqlFunction = { documentation: 'Returns 1 if valid, 0 if invalid' }; +const json_keys: DocumentedSqlFunction = { + debugName: 'json_keys', + call(json: SqliteValue) { + const jsonString = castAsText(json); + if (jsonString == null) { + return null; + } + + const jsonParsed = JSONBig.parse(jsonString); + if (typeof jsonParsed != 'object') { + throw new Error(`Cannot call json_keys on a scalar`); + } else if (Array.isArray(jsonParsed)) { + throw new Error(`Cannot call json_keys on an array`); + } + const keys = Object.keys(jsonParsed as {}); + // Keys are always strings, safe to use plain JSON. + return JSON.stringify(keys); + }, + parameters: [{ name: 'json', type: ExpressionType.ANY, optional: false }], + getReturnType(args) { + // TODO: proper nullable types + return ExpressionType.TEXT; + }, + detail: 'Returns the keys of a JSON object as a JSON array' +}; + const unixepoch: DocumentedSqlFunction = { debugName: 'unixepoch', call(value?: SqliteValue, specifier?: SqliteValue, specifier2?: SqliteValue) { @@ -401,6 +482,7 @@ const st_y: DocumentedSqlFunction = { export const SQL_FUNCTIONS_NAMED = { upper, lower, + substring, hex, length, base64, @@ -409,6 +491,7 @@ export const SQL_FUNCTIONS_NAMED = { json_extract, json_array_length, json_valid, + json_keys, unixepoch, datetime, st_asgeojson, diff --git a/packages/sync-rules/test/src/sql_functions.test.ts b/packages/sync-rules/test/src/sql_functions.test.ts index 525133c3b..1a155d6af 100644 --- a/packages/sync-rules/test/src/sql_functions.test.ts +++ b/packages/sync-rules/test/src/sql_functions.test.ts @@ -44,6 +44,30 @@ describe('SQL functions', () => { expect(fn.json_array_length(`{"a":[1,2,3,4]}`)).toEqual(0n); }); + test('json_keys', () => { + expect(fn.json_keys(`{"a": 1, "b": "2", "0": "test", "c": {"d": "e"}}`)).toEqual(`["0","a","b","c"]`); + expect(fn.json_keys(`{}`)).toEqual(`[]`); + expect(fn.json_keys(null)).toEqual(null); + expect(fn.json_keys()).toEqual(null); + expect(() => fn.json_keys(`{"a": 1, "a": 2}`)).toThrow(); + expect(() => fn.json_keys(`[1,2,3]`)).toThrow(); + expect(() => fn.json_keys(3)).toThrow(); + }); + + test('json_valid', () => { + expect(fn.json_valid(`{"a": 1, "b": "2", "0": "test", "c": {"d": "e"}}`)).toEqual(1n); + expect(fn.json_valid(`{}`)).toEqual(1n); + expect(fn.json_valid(null)).toEqual(0n); + expect(fn.json_valid()).toEqual(0n); + expect(fn.json_valid(`{"a": 1, "a": 2}`)).toEqual(0n); + expect(fn.json_valid(`[1,2,3]`)).toEqual(1n); + expect(fn.json_valid(3)).toEqual(1n); + expect(fn.json_valid('test')).toEqual(0n); + expect(fn.json_valid('"test"')).toEqual(1n); + expect(fn.json_valid('true')).toEqual(1n); + expect(fn.json_valid('TRUE')).toEqual(0n); + }); + test('typeof', () => { expect(fn.typeof(null)).toEqual('null'); expect(fn.typeof('test')).toEqual('text'); @@ -101,6 +125,22 @@ describe('SQL functions', () => { expect(fn.lower(Uint8Array.of(0x61, 0x62, 0x43))).toEqual('abc'); }); + test('substring', () => { + expect(fn.substring(null)).toEqual(null); + expect(fn.substring('abc')).toEqual(null); + expect(fn.substring('abcde', 2, 3)).toEqual('bcd'); + expect(fn.substring('abcde', 2)).toEqual('bcde'); + expect(fn.substring('abcde', 2, null)).toEqual(null); + expect(fn.substring('abcde', 0, 1)).toEqual(''); + expect(fn.substring('abcde', 0, 2)).toEqual('a'); + expect(fn.substring('abcde', 1, 2)).toEqual('ab'); + expect(fn.substring('abcde', -2)).toEqual('de'); + expect(fn.substring('abcde', -2, 1)).toEqual('d'); + expect(fn.substring('abcde', 6, -5)).toEqual('abcde'); + expect(fn.substring('abcde', 5, -2)).toEqual('cd'); + expect(fn.substring('2023-06-28 14:12:00.999Z', 1, 10)).toEqual('2023-06-28'); + }); + test('cast', () => { expect(cast(null, 'text')).toEqual(null); expect(cast(null, 'integer')).toEqual(null); diff --git a/service/CHANGELOG.md b/service/CHANGELOG.md index 6c12e7529..fc767a8cf 100644 --- a/service/CHANGELOG.md +++ b/service/CHANGELOG.md @@ -1,5 +1,22 @@ # @powersync/service-image +## 0.5.7 + +### Patch Changes + +- Updated dependencies [6b72e6c] + - @powersync/service-core@0.8.7 + +## 0.5.6 + +### Patch Changes + +- Updated dependencies [2d3bb6a] +- Updated dependencies [17a6db0] +- Updated dependencies [0f90b02] + - @powersync/service-core@0.8.6 + - @powersync/service-sync-rules@0.20.0 + ## 0.5.5 ### Patch Changes diff --git a/service/README.md b/service/README.md index 810e45a54..668d4575d 100644 --- a/service/README.md +++ b/service/README.md @@ -2,7 +2,7 @@

-_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres or MongoDB on the server-side (MySQL coming soon)._ +_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB or MySQL on the server-side._ # Quick reference diff --git a/service/package.json b/service/package.json index 95e1f5296..33caf860f 100644 --- a/service/package.json +++ b/service/package.json @@ -1,6 +1,6 @@ { "name": "@powersync/service-image", - "version": "0.5.5", + "version": "0.5.7", "private": true, "license": "FSL-1.1-Apache-2.0", "type": "module", diff --git a/test-client/CHANGELOG.md b/test-client/CHANGELOG.md index 9c90f775c..21411c998 100644 --- a/test-client/CHANGELOG.md +++ b/test-client/CHANGELOG.md @@ -1,5 +1,20 @@ # test-client +## 0.1.9 + +### Patch Changes + +- Updated dependencies [6b72e6c] + - @powersync/service-core@0.8.7 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies [2d3bb6a] +- Updated dependencies [17a6db0] + - @powersync/service-core@0.8.6 + ## 0.1.7 ### Patch Changes diff --git a/test-client/package.json b/test-client/package.json index 5e311c708..e4eb17e92 100644 --- a/test-client/package.json +++ b/test-client/package.json @@ -2,7 +2,7 @@ "name": "test-client", "repository": "https://github.com/powersync-ja/powersync-service", "private": true, - "version": "0.1.7", + "version": "0.1.9", "main": "dist/index.js", "bin": "dist/bin.js", "license": "Apache-2.0",