Compare commits
8 Commits
chore/sdk-
...
feat/datab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4685acc977 | ||
|
|
bb9aaf2903 | ||
|
|
8e82edd0c6 | ||
|
|
b0256da33f | ||
|
|
351daa5fbe | ||
|
|
4e9de6a764 | ||
|
|
9dab347348 | ||
|
|
6c044458ca |
@@ -49,4 +49,4 @@ This repository is a monorepo that contains multiple packages and applications.
|
||||
- `tools/codegen` - Internal code generation tool to build the SDK
|
||||
- `tools/mintlify-openapi` - Internal tool to generate reference documentation for Mintlify from an OpenAPI spec.
|
||||
|
||||
For details about those projects and how to contribure, please refer to their respective `README.md` and `CONTRIBUTING.md` files.
|
||||
For details about those projects and how to contribute, please refer to their respective `README.md` and `CONTRIBUTING.md` files.
|
||||
|
||||
@@ -107,6 +107,7 @@ Nhost is frontend agnostic, which means Nhost works with all frontend frameworks
|
||||
# Resources
|
||||
|
||||
- Start developing locally with the [Nhost CLI](https://docs.nhost.io/platform/cli/local-development)
|
||||
|
||||
## Nhost Clients
|
||||
|
||||
- [JavaScript/TypeScript](https://docs.nhost.io/reference/javascript/nhost-js/main)
|
||||
@@ -137,7 +138,7 @@ Here are some ways of contributing to making Nhost better:
|
||||
|
||||
- **[Try out Nhost](https://docs.nhost.io)**, and think of ways to make the service better. Let us know here on GitHub.
|
||||
- Join our [Discord](https://discord.com/invite/9V7Qb2U) and connect with other members to share and learn from.
|
||||
- Send a pull request to any of our [open source repositories](https://github.com/nhost) on Github. Check our [contribution guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) and our [developers guide](https://github.com/nhost/nhost/blob/main/DEVELOPERS.md) for more details about how to contribute. We're looking forward to your contribution!
|
||||
- Send a pull request to any of our [open source repositories](https://github.com/nhost) on Github. Check out our [contribution guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) for more details about how to contribute. We're looking forward to your contribution!
|
||||
|
||||
### Contributors
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": [
|
||||
"GHSA-9965-vmph-33xx", // https://github.com/advisories/GHSA-9965-vmph-33xx Update package once have a fix
|
||||
"GHSA-7mvr-c777-76hp" // https://github.com/advisories/GHSA-7mvr-c777-76hp Update package once Nix side is also updated
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@
|
||||
"timezones-list": "^3.1.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.11.0",
|
||||
"validator": "^13.15.20",
|
||||
"yup": "^1.4.0",
|
||||
"yup-password": "^0.2.2",
|
||||
"zod": "^3.23.8"
|
||||
@@ -232,7 +232,9 @@
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild@<=0.24.2": ">=0.25.0"
|
||||
"esbuild@<=0.24.2": ">=0.25.0",
|
||||
"js-yaml@<=4.1.0": ">=4.1.1",
|
||||
"glob@>=10.3.7 <=11.0.3": ">=11.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
128
dashboard/pnpm-lock.yaml
generated
128
dashboard/pnpm-lock.yaml
generated
@@ -6,6 +6,8 @@ settings:
|
||||
|
||||
overrides:
|
||||
esbuild@<=0.24.2: '>=0.25.0'
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
glob@>=10.3.7 <=11.0.3: '>=11.1.0'
|
||||
|
||||
packageExtensionsChecksum: sha256-gRFeykwiwMfEE6etcYx6N48XwVeKzxbqNveL7KTQgSQ=
|
||||
|
||||
@@ -320,8 +322,8 @@ importers:
|
||||
specifier: ^9.0.1
|
||||
version: 9.0.1
|
||||
validator:
|
||||
specifier: ^13.11.0
|
||||
version: 13.12.0
|
||||
specifier: ^13.15.20
|
||||
version: 13.15.23
|
||||
yup:
|
||||
specifier: ^1.4.0
|
||||
version: 1.5.0
|
||||
@@ -2245,6 +2247,14 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2618,10 +2628,6 @@ packages:
|
||||
'@orval/zod@7.11.2':
|
||||
resolution: {integrity: sha512-4MzTg5Wms8/LlM3CbYu80dvCbP88bVlQjnYsBdFXuEv0K2GYkBCAhVOrmXCVrPXE89neV6ABkvWQeuKZQpkdxQ==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@playwright/test@1.54.1':
|
||||
resolution: {integrity: sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5649,8 +5655,8 @@ packages:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
foreground-child@3.1.1:
|
||||
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.4:
|
||||
@@ -5779,8 +5785,9 @@ packages:
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
glob@12.0.0:
|
||||
resolution: {integrity: sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
glob@7.1.7:
|
||||
@@ -6288,9 +6295,9 @@ packages:
|
||||
iterator.prototype@1.1.2:
|
||||
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
|
||||
|
||||
jackspeak@3.2.3:
|
||||
resolution: {integrity: sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw==}
|
||||
engines: {node: '>=14'}
|
||||
jackspeak@4.1.1:
|
||||
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
jest-diff@29.7.0:
|
||||
resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
|
||||
@@ -6337,8 +6344,8 @@ packages:
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsdom@22.1.0:
|
||||
@@ -6555,9 +6562,9 @@ packages:
|
||||
lowlight@3.1.0:
|
||||
resolution: {integrity: sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ==}
|
||||
|
||||
lru-cache@10.2.2:
|
||||
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
lru-cache@11.2.2:
|
||||
resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
@@ -6798,6 +6805,10 @@ packages:
|
||||
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
|
||||
hasBin: true
|
||||
|
||||
minimatch@10.1.1:
|
||||
resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
@@ -6809,10 +6820,6 @@ packages:
|
||||
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimatch@9.0.4:
|
||||
resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -7181,9 +7188,9 @@ packages:
|
||||
resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
path-scurry@2.0.1:
|
||||
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
path-to-regexp@6.3.0:
|
||||
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
|
||||
@@ -8594,8 +8601,8 @@ packages:
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
validator@13.12.0:
|
||||
resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==}
|
||||
validator@13.15.23:
|
||||
resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
vfile-message@4.0.2:
|
||||
@@ -8936,7 +8943,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jsdevtools/ono': 7.1.3
|
||||
'@types/json-schema': 7.0.15
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
|
||||
'@apidevtools/openapi-schemas@2.1.0': {}
|
||||
|
||||
@@ -10335,7 +10342,7 @@ snapshots:
|
||||
globals: 13.24.0
|
||||
ignore: 5.3.2
|
||||
import-fresh: 3.3.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
minimatch: 3.1.2
|
||||
strip-json-comments: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
@@ -11019,7 +11026,7 @@ snapshots:
|
||||
loglevel: 1.9.2
|
||||
loglevel-plugin-prefix: 0.8.4
|
||||
minimatch: 6.2.0
|
||||
validator: 13.12.0
|
||||
validator: 13.15.23
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
@@ -11152,11 +11159,17 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.14.8
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
dependencies:
|
||||
'@isaacs/balanced-match': 4.0.1
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
string-width-cjs: string-width@4.2.3
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi: 7.1.2
|
||||
strip-ansi-cjs: strip-ansi@6.0.1
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
@@ -11604,9 +11617,6 @@ snapshots:
|
||||
- openapi-types
|
||||
- supports-color
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@playwright/test@1.54.1':
|
||||
dependencies:
|
||||
playwright: 1.54.1
|
||||
@@ -14198,7 +14208,7 @@ snapshots:
|
||||
cosmiconfig@8.3.6(typescript@5.8.3):
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
optionalDependencies:
|
||||
@@ -14208,7 +14218,7 @@ snapshots:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
@@ -15062,7 +15072,7 @@ snapshots:
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
is-path-inside: 3.0.3
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
levn: 0.4.1
|
||||
lodash.merge: 4.6.2
|
||||
@@ -15241,7 +15251,7 @@ snapshots:
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
|
||||
foreground-child@3.1.1:
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
@@ -15382,14 +15392,14 @@ snapshots:
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
glob@10.4.5:
|
||||
glob@12.0.0:
|
||||
dependencies:
|
||||
foreground-child: 3.1.1
|
||||
jackspeak: 3.2.3
|
||||
minimatch: 9.0.4
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 4.1.1
|
||||
minimatch: 10.1.1
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
path-scurry: 2.0.1
|
||||
|
||||
glob@7.1.7:
|
||||
dependencies:
|
||||
@@ -15912,11 +15922,9 @@ snapshots:
|
||||
reflect.getprototypeof: 1.0.8
|
||||
set-function-name: 2.0.2
|
||||
|
||||
jackspeak@3.2.3:
|
||||
jackspeak@4.1.1:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jest-diff@29.7.0:
|
||||
dependencies:
|
||||
@@ -15973,7 +15981,7 @@ snapshots:
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -16210,7 +16218,7 @@ snapshots:
|
||||
devlop: 1.1.0
|
||||
highlight.js: 11.9.0
|
||||
|
||||
lru-cache@10.2.2: {}
|
||||
lru-cache@11.2.2: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
@@ -16640,6 +16648,10 @@ snapshots:
|
||||
|
||||
mini-svg-data-uri@1.4.4: {}
|
||||
|
||||
minimatch@10.1.1:
|
||||
dependencies:
|
||||
'@isaacs/brace-expansion': 5.0.0
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
@@ -16652,10 +16664,6 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.4:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
@@ -17094,9 +17102,9 @@ snapshots:
|
||||
dependencies:
|
||||
path-root-regex: 0.1.2
|
||||
|
||||
path-scurry@1.11.1:
|
||||
path-scurry@2.0.1:
|
||||
dependencies:
|
||||
lru-cache: 10.2.2
|
||||
lru-cache: 11.2.2
|
||||
minipass: 7.1.2
|
||||
|
||||
path-to-regexp@6.3.0: {}
|
||||
@@ -17942,7 +17950,7 @@ snapshots:
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
string-width@7.2.0:
|
||||
dependencies:
|
||||
@@ -18061,7 +18069,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
commander: 4.1.1
|
||||
glob: 10.4.5
|
||||
glob: 12.0.0
|
||||
lines-and-columns: 1.2.4
|
||||
mz: 2.7.0
|
||||
pirates: 4.0.6
|
||||
@@ -18163,7 +18171,7 @@ snapshots:
|
||||
test-exclude@7.0.1:
|
||||
dependencies:
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
glob: 10.4.5
|
||||
glob: 12.0.0
|
||||
minimatch: 9.0.5
|
||||
|
||||
text-table@0.2.0: {}
|
||||
@@ -18535,7 +18543,7 @@ snapshots:
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
||||
validator@13.12.0: {}
|
||||
validator@13.15.23: {}
|
||||
|
||||
vfile-message@4.0.2:
|
||||
dependencies:
|
||||
@@ -18789,9 +18797,9 @@ snapshots:
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
ansi-styles: 6.2.3
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
wrap-ansi@9.0.0:
|
||||
dependencies:
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Button } from '@/components/ui/v3/button';
|
||||
import { Input } from '@/components/ui/v3/input';
|
||||
import {
|
||||
useDataGridFilter,
|
||||
type DataGridFilterOperator,
|
||||
} from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { cn, isNotEmptyValue } from '@/lib/utils';
|
||||
import { X } from 'lucide-react';
|
||||
import DataGridFilterColumn from './DataGridFilterColumn';
|
||||
import DataGridFilterOperators from './DataGridFilterOperators';
|
||||
|
||||
type FilterProps = {
|
||||
column: string;
|
||||
op: DataGridFilterOperator;
|
||||
value: string;
|
||||
index: number;
|
||||
columns: Array<{ id: string; dataType: string }>;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
function DataGridFilter({
|
||||
column,
|
||||
op,
|
||||
value,
|
||||
index,
|
||||
columns,
|
||||
error,
|
||||
}: FilterProps) {
|
||||
const { setColumn, setOp, setValue, removeFilter } = useDataGridFilter();
|
||||
|
||||
function handleOpChange(newOp: DataGridFilterOperator) {
|
||||
setOp(index, newOp);
|
||||
if (
|
||||
newOp === '$like' ||
|
||||
newOp === '$ilike' ||
|
||||
newOp === '$nlike' ||
|
||||
newOp === '$nilike'
|
||||
) {
|
||||
setValue(index, '%%');
|
||||
} else if (newOp === '$in' || newOp === '$nin') {
|
||||
setValue(index, '[]');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<DataGridFilterColumn
|
||||
value={column}
|
||||
onChange={(newColumn) => setColumn(index, newColumn)}
|
||||
columns={columns}
|
||||
/>
|
||||
<DataGridFilterOperators value={op} onChange={handleOpChange} />
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
className={cn('h-8 p-2', {
|
||||
'border-destructive': isNotEmptyValue(error),
|
||||
})}
|
||||
placeholder="Enter a value"
|
||||
value={value}
|
||||
onChange={(event) => setValue(index, event.target.value)}
|
||||
/>
|
||||
<span
|
||||
className={`inline-flex h-[0.875rem] text-xs- text-destructive ${isNotEmptyValue(error) ? 'visible' : 'invisible'}`}
|
||||
>
|
||||
{error}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="flex-i h-8 w-8"
|
||||
onClick={() => removeFilter(index)}
|
||||
>
|
||||
<X width={12} height={12} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataGridFilter;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Badge } from '@/components/ui/v3/badge';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from '@/components/ui/v3/select';
|
||||
|
||||
type DataFilterColumnProps = {
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
columns: Array<{ id: string; dataType: string }>;
|
||||
};
|
||||
|
||||
function DataGrdiFitlerColumn({
|
||||
value,
|
||||
onChange,
|
||||
columns,
|
||||
}: DataFilterColumnProps) {
|
||||
return (
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger className="mp-2 h-8 max-w-[35%]">
|
||||
<span className="!inline-block w-4/5 justify-start overflow-ellipsis text-left">
|
||||
{value}
|
||||
</span>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{columns.map((column) => (
|
||||
<SelectItem key={column.id} value={column.id}>
|
||||
{column.id}{' '}
|
||||
<Badge className="rounded-sm+ bg-secondary p-1 text-[0.75rem] font-normal leading-[0.75]">
|
||||
{/* TODO: Fix type */}
|
||||
{(column as any).dataType}
|
||||
</Badge>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataGrdiFitlerColumn;
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from '@/components/ui/v3/select';
|
||||
import type { DataGridFilterOperator } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
|
||||
const OPERATORS = [
|
||||
{ op: '$eq', label: '[$eq] equals' },
|
||||
{ op: '$ne', label: '[$ne] not equals' },
|
||||
{ op: '$in', label: '[$in] in' },
|
||||
{ op: '$nin', label: '[$nin] not in' },
|
||||
{ op: '$gt', label: '[$gt] >' },
|
||||
{ op: '$lt', label: '[$lt] <' },
|
||||
{ op: '$gte', label: '[$gte] >=' },
|
||||
{ op: '$lte', label: '[$lte] <=' },
|
||||
{ op: '$like', label: '[$like] like' },
|
||||
{ op: '$nlike', label: '[$nlike] not like' },
|
||||
{ op: '$ilike', label: '[$ilike] like (case-insensitive)' },
|
||||
{ op: '$nilike', label: '[$nilike] not like (case-insensitive)' },
|
||||
{ op: '$similar', label: '[$similar] similar' },
|
||||
{ op: '$nsimilar', label: '[$nsimilar] not similar' },
|
||||
{ op: '$regex', label: '[$regex] ~' },
|
||||
{ op: '$iregex', label: '[$iregex] ~*' },
|
||||
{ op: '$nregex', label: '[$nregex] !~' },
|
||||
{ op: '$niregex', label: '[$niregex] !~*' },
|
||||
];
|
||||
|
||||
type DataFilterProps = {
|
||||
value: DataGridFilterOperator;
|
||||
onChange: (newOp: DataGridFilterOperator) => void;
|
||||
};
|
||||
|
||||
function DataGridOperators({ value, onChange }: DataFilterProps) {
|
||||
return (
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger className="h-8 w-[6rem] p-2">{value}</SelectTrigger>
|
||||
<SelectContent>
|
||||
{OPERATORS.map(({ op, label }) => (
|
||||
<SelectItem key={op} value={op}>
|
||||
<span>[{op}]</span> <span className="text-secondary">{label}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataGridOperators;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Button, type ButtonProps } from '@/components/ui/v3/button';
|
||||
import { useDataGridFilter } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Funnel } from 'lucide-react';
|
||||
import { type ForwardedRef, forwardRef } from 'react';
|
||||
|
||||
function DataBrowserCustomizerTrigger(
|
||||
props: ButtonProps,
|
||||
ref: ForwardedRef<HTMLButtonElement>,
|
||||
) {
|
||||
const { appliedFilters } = useDataGridFilter();
|
||||
const numberOfAppliedFilters = appliedFilters.length;
|
||||
|
||||
const { className, ...buttonProps } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn('relative', className)}
|
||||
{...buttonProps}
|
||||
>
|
||||
<Funnel />
|
||||
{numberOfAppliedFilters > 0 && (
|
||||
<span className="absolute bottom-[6px] right-[6px] w-[0.725rem] rounded-full bg-primary-text p-0 text-[0.725rem] leading-none text-paper">
|
||||
{numberOfAppliedFilters}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(DataBrowserCustomizerTrigger);
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Button } from '@/components/ui/v3/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/v3/popover';
|
||||
import {
|
||||
type DataGridFilter as Filter,
|
||||
useDataGridFilter,
|
||||
} from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||
import { isEmptyValue, isNotEmptyValue } from '@/lib/utils';
|
||||
import { useState } from 'react';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import DataGridFilter from './DataGridFilter';
|
||||
import DataGridFilterTrigger from './DataGridFilterTrigger';
|
||||
|
||||
function hasErrors(filters: Filter[]) {
|
||||
return filters.reduce((errors, { op, value, column }, index) => {
|
||||
if (isEmptyValue(value)) {
|
||||
return { ...errors, [`${column}.${index}`]: 'Empty filter' };
|
||||
}
|
||||
if (['$in', '$nin'].includes(op)) {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
} catch {
|
||||
return {
|
||||
...errors,
|
||||
[`${column}.${index}`]: 'Invalid format. ["item1","item 2"]',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function DataGridFilters() {
|
||||
const { filters, addFilter, appliedFilters, setFilters, setAppliedFilters } =
|
||||
useDataGridFilter();
|
||||
const { columns } = useDataGridConfig();
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
function resetFilters() {
|
||||
setFilters(appliedFilters);
|
||||
}
|
||||
|
||||
function handleApplyFilter() {
|
||||
const filterErrors = hasErrors(filters);
|
||||
setErrors(filterErrors);
|
||||
if (isEmptyValue(filterErrors)) {
|
||||
setAppliedFilters(filters);
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenChange(newOpenState: boolean) {
|
||||
if (!newOpenState) {
|
||||
resetFilters();
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddFilter() {
|
||||
addFilter({ column: columns[0].id, op: '$eq', value: '', id: uuidV4() });
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover onOpenChange={handleOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<DataGridFilterTrigger />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="flex w-[40rem] flex-col gap-6 p-0">
|
||||
<div className="flex w-full flex-col gap-0 px-3 pb-0 pt-6">
|
||||
{isNotEmptyValue(filters) &&
|
||||
filters.map((filter, index) => (
|
||||
<DataGridFilter
|
||||
{...filter}
|
||||
key={filter.id}
|
||||
index={index}
|
||||
columns={columns as any}
|
||||
error={errors[`${filter.column}.${index}`]}
|
||||
/>
|
||||
))}
|
||||
{isEmptyValue(filters) && (
|
||||
<p>
|
||||
<strong>No filters applied to this table</strong>
|
||||
<br />
|
||||
Add a filter below to filter the table
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t-1 border-t-[#e2e8f0] p-3 dark:border-t-[#2f363d]">
|
||||
<Button variant="outline" size="sm" onClick={handleAddFilter}>
|
||||
Add filter
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleApplyFilter}>
|
||||
Apply filter
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataGridFilters;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DataGridFilters } from './DataGridFilters';
|
||||
@@ -9,7 +9,6 @@ export default function useTablePath() {
|
||||
const {
|
||||
query: { dataSourceSlug, schemaSlug, tableSlug },
|
||||
} = useRouter();
|
||||
|
||||
if (!dataSourceSlug || !schemaSlug || !tableSlug) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FormActivityIndicator } from '@/components/form/FormActivityIndicator';
|
||||
import { InlineCode } from '@/components/ui/v3/inline-code';
|
||||
import { useTablePath } from '@/features/orgs/projects/database/common/hooks/useTablePath';
|
||||
import { DataBrowserEmptyState } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserEmptyState';
|
||||
import { useDataGridFilter } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { DataBrowserGridControls } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGridControls';
|
||||
import { useTableQuery } from '@/features/orgs/projects/database/dataGrid/hooks/useTableQuery';
|
||||
import type { UpdateRecordVariables } from '@/features/orgs/projects/database/dataGrid/hooks/useUpdateRecordMutation';
|
||||
@@ -26,6 +27,7 @@ import { DataGridNumericCell } from '@/features/orgs/projects/storage/dataGrid/c
|
||||
import { DataGridTextCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridTextCell';
|
||||
import { isNotEmptyValue } from '@/lib/utils';
|
||||
|
||||
import { useTableRows } from '@/features/orgs/projects/database/dataGrid/hooks/useTableRows';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { KeyRound } from 'lucide-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -157,16 +159,45 @@ export default function DataBrowserGrid({
|
||||
const [currentOffset, setCurrentOffset] = useState<number>(
|
||||
parseInt(page as string, 10) - 1 || 0,
|
||||
);
|
||||
|
||||
const { appliedFilters } = useDataGridFilter();
|
||||
const { mutateAsync: updateRow } = useUpdateRecordWithToastMutation();
|
||||
|
||||
const sortByString = isNotEmptyValue(sortBy?.[0])
|
||||
? `${sortBy[0].id}.${sortBy[0].desc}`
|
||||
: 'default-order';
|
||||
const filterString = isNotEmptyValue(appliedFilters)
|
||||
? appliedFilters
|
||||
.map((filter) => `${filter.column}-${filter.op}-${filter.value}`)
|
||||
.join('')
|
||||
: 'no-filter';
|
||||
|
||||
const { data, status, error, refetch } = useTableQuery(
|
||||
[currentTablePath, currentOffset, sortByString],
|
||||
const {
|
||||
data,
|
||||
status,
|
||||
error,
|
||||
refetch,
|
||||
isFetching: isTableDataFetching,
|
||||
} = useTableQuery([currentTablePath], {
|
||||
limit,
|
||||
offset: currentOffset * limit,
|
||||
orderBy:
|
||||
sortBy?.map(({ id, desc }) => ({
|
||||
columnName: id,
|
||||
mode: desc ? 'DESC' : 'ASC',
|
||||
})) || [],
|
||||
filters: appliedFilters,
|
||||
});
|
||||
|
||||
const { columns, metadata } = data || {
|
||||
columns: [] as NormalizedQueryDataRow[],
|
||||
};
|
||||
|
||||
const columnNames = columns?.map((column) => column.column_name);
|
||||
|
||||
const { data: tableRowsData, isFetching: isTableRowsFetching } = useTableRows(
|
||||
[currentTablePath, currentOffset, sortByString, filterString],
|
||||
{
|
||||
columnNames,
|
||||
limit,
|
||||
offset: currentOffset * limit,
|
||||
orderBy:
|
||||
@@ -174,15 +205,16 @@ export default function DataBrowserGrid({
|
||||
columnName: id,
|
||||
mode: desc ? 'DESC' : 'ASC',
|
||||
})) || [],
|
||||
filters: appliedFilters,
|
||||
queryOptions: {
|
||||
enabled: !isTableDataFetching,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { columns, rows, numberOfRows, metadata } = data || {
|
||||
columns: [] as NormalizedQueryDataRow[],
|
||||
rows: [] as NormalizedQueryDataRow[],
|
||||
numberOfRows: 0,
|
||||
};
|
||||
|
||||
const rows = tableRowsData?.rows || ([] as NormalizedQueryDataRow[]);
|
||||
const numberOfRows = tableRowsData?.numberOfRows || 0;
|
||||
const tableRowsError = tableRowsData?.error;
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentTablePath &&
|
||||
@@ -262,8 +294,6 @@ export default function DataBrowserGrid({
|
||||
[columns, currentTablePath, queryClient, updateRow],
|
||||
);
|
||||
|
||||
const memoizedData = useMemo(() => rows, [rows]);
|
||||
|
||||
async function handleInsertRowClick() {
|
||||
openDrawer({
|
||||
title: 'Insert a New Row',
|
||||
@@ -324,11 +354,17 @@ export default function DataBrowserGrid({
|
||||
<DataGrid
|
||||
ref={dataGridRef}
|
||||
columns={memoizedColumns}
|
||||
data={memoizedData}
|
||||
data={rows}
|
||||
allowSelection
|
||||
allowResize
|
||||
allowSort
|
||||
emptyStateMessage="No rows found."
|
||||
emptyStateMessage={
|
||||
tableRowsError ? (
|
||||
<span className="text-destructive">Error: {tableRowsError}</span>
|
||||
) : (
|
||||
'No rows found.'
|
||||
)
|
||||
}
|
||||
loading={status === 'loading'}
|
||||
sortBy={sortBy}
|
||||
className="pb-17 sm:pb-0"
|
||||
@@ -352,6 +388,7 @@ export default function DataBrowserGrid({
|
||||
refetchData={refetch}
|
||||
/>
|
||||
}
|
||||
isFetching={!!isTableRowsFetching}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import { useTablePath } from '@/features/orgs/projects/database/common/hooks/useTablePath';
|
||||
import PersistenDataGrdiFilterStorage from '@/features/orgs/projects/database/dataGrid/utils/PersistentDataGridFilterStorage';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
} from 'react';
|
||||
|
||||
function updateFilterInArray(
|
||||
filters: DataGridFilter[],
|
||||
index: number,
|
||||
newValue: DataGridFilter,
|
||||
) {
|
||||
return [...filters.slice(0, index), newValue, ...filters.slice(index + 1)];
|
||||
}
|
||||
|
||||
function updateFilter(
|
||||
oldFilters: DataGridFilter[],
|
||||
index: number,
|
||||
filterKey: keyof DataGridFilter,
|
||||
newValue: string | DataGridFilterOperator,
|
||||
) {
|
||||
const filter = oldFilters[index];
|
||||
const filterToUpdate = {
|
||||
...filter,
|
||||
[filterKey]: newValue,
|
||||
};
|
||||
return updateFilterInArray(oldFilters, index, filterToUpdate);
|
||||
}
|
||||
|
||||
export type DataGridFilterOperator =
|
||||
| '$eq'
|
||||
| '$ne'
|
||||
| '$in'
|
||||
| '$nin'
|
||||
| '$gt'
|
||||
| '$lt'
|
||||
| '$gte'
|
||||
| '$lte'
|
||||
| '$like'
|
||||
| '$nlike'
|
||||
| '$ilike'
|
||||
| '$nilike'
|
||||
| '$similar'
|
||||
| '$nsimilar'
|
||||
| '$regex'
|
||||
| '$iregex'
|
||||
| '$nregex'
|
||||
| '$niregex';
|
||||
|
||||
export type DataGridFilter = {
|
||||
column: string;
|
||||
op: DataGridFilterOperator;
|
||||
value: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type DataGridFilterContextProps = {
|
||||
appliedFilters: DataGridFilter[];
|
||||
setAppliedFilters: (filters: DataGridFilter[]) => void;
|
||||
filters: DataGridFilter[];
|
||||
setFilters: (filters: DataGridFilter[]) => void;
|
||||
addFilter: (newFilter: DataGridFilter) => void;
|
||||
removeFilter: (index: number) => void;
|
||||
setValue: (index: number, newValue: string) => void;
|
||||
setColumn: (index: number, newColumn: string) => void;
|
||||
setOp: (index: number, newOp: DataGridFilterOperator) => void;
|
||||
};
|
||||
|
||||
const DataGridFilterContext = createContext<DataGridFilterContextProps>({
|
||||
appliedFilters: [] as DataGridFilter[],
|
||||
setAppliedFilters: () => {},
|
||||
filters: [] as DataGridFilter[],
|
||||
setFilters: () => {},
|
||||
addFilter: () => {},
|
||||
removeFilter: () => {},
|
||||
setValue: () => {},
|
||||
setColumn: () => {},
|
||||
setOp: () => {},
|
||||
});
|
||||
|
||||
function DataGridFilterProvider({ children }: PropsWithChildren) {
|
||||
const tablePath = useTablePath();
|
||||
const [appliedFilters, _setAppliedFilters] = useState<DataGridFilter[]>(() =>
|
||||
PersistenDataGrdiFilterStorage.getDataGridFilters(tablePath),
|
||||
);
|
||||
const [filters, setFilters] = useState<DataGridFilter[]>(() =>
|
||||
PersistenDataGrdiFilterStorage.getDataGridFilters(tablePath),
|
||||
);
|
||||
// const [loadedFiltersTablePath, setLoadedFiltersTablePath] = useState(
|
||||
// () => tablePath,
|
||||
// );
|
||||
|
||||
// const test = useRef<string | null>(null);
|
||||
|
||||
function addFilter(newFilter: DataGridFilter) {
|
||||
setFilters((oldFilters) => oldFilters.concat(newFilter));
|
||||
}
|
||||
|
||||
function setAppliedFilters(newFilters: DataGridFilter[]) {
|
||||
_setAppliedFilters(newFilters);
|
||||
PersistenDataGrdiFilterStorage.saveDataGridFilters(tablePath, newFilters);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const filtersForTheTable =
|
||||
PersistenDataGrdiFilterStorage.getDataGridFilters(tablePath);
|
||||
setFilters(filtersForTheTable);
|
||||
_setAppliedFilters(filtersForTheTable);
|
||||
}, [tablePath]);
|
||||
|
||||
const contextValue: DataGridFilterContextProps = useMemo(
|
||||
() => ({
|
||||
filters,
|
||||
setFilters,
|
||||
appliedFilters,
|
||||
setAppliedFilters,
|
||||
addFilter,
|
||||
removeFilter(index: number) {
|
||||
setFilters((oldFilters) => {
|
||||
const newFilters = oldFilters.filter((_, i) => index !== i);
|
||||
PersistenDataGrdiFilterStorage.saveDataGridFilters(
|
||||
tablePath,
|
||||
newFilters,
|
||||
);
|
||||
return newFilters;
|
||||
});
|
||||
},
|
||||
|
||||
setColumn(index: number, newColumnValue: string) {
|
||||
setFilters((oldFilters) =>
|
||||
updateFilter(oldFilters, index, 'column', newColumnValue),
|
||||
);
|
||||
},
|
||||
|
||||
setValue(index: number, newValue: string) {
|
||||
setFilters((oldFilters) =>
|
||||
updateFilter(oldFilters, index, 'value', newValue),
|
||||
);
|
||||
},
|
||||
|
||||
setOp(index: number, newOp: DataGridFilterOperator) {
|
||||
setFilters((oldFilters) =>
|
||||
updateFilter(oldFilters, index, 'op', newOp),
|
||||
);
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[appliedFilters, filters],
|
||||
);
|
||||
|
||||
return (
|
||||
<DataGridFilterContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</DataGridFilterContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataGridFilterProvider;
|
||||
|
||||
export function useDataGridFilter() {
|
||||
const context = useContext(DataGridFilterContext);
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as DataGridFilterProvider } from './DataGridFilterProvider';
|
||||
|
||||
export * from './DataGridFilterProvider';
|
||||
@@ -2,6 +2,7 @@ import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Badge } from '@/components/ui/v3/badge';
|
||||
import { ButtonWithLoading as Button } from '@/components/ui/v3/button';
|
||||
import { DataGridCustomizerControls } from '@/features/orgs/projects/common/components/DataGridCustomizerControls';
|
||||
import { DataGridFilters } from '@/features/orgs/projects/common/components/DataGridFilters';
|
||||
import { useDeleteRecordMutation } from '@/features/orgs/projects/database/dataGrid/hooks/useDeleteRecordMutation';
|
||||
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||
@@ -156,6 +157,7 @@ export default function DataBrowserGridControls({
|
||||
{...restPaginationProps}
|
||||
/>
|
||||
)}
|
||||
<DataGridFilters />
|
||||
<DataGridCustomizerControls />
|
||||
<Button onClick={onInsertRowClick} size="sm">
|
||||
<Plus className="h-4 w-4" /> Insert row
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DataGridFilter } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import type {
|
||||
ForeignKeyRelation,
|
||||
MutationOrQueryBaseOptions,
|
||||
@@ -9,7 +10,6 @@ import type {
|
||||
import { extractForeignKeyRelation } from '@/features/orgs/projects/database/dataGrid/utils/extractForeignKeyRelation';
|
||||
import { getPreparedReadOnlyHasuraQuery } from '@/features/orgs/projects/database/dataGrid/utils/hasuraQueryHelpers';
|
||||
import { POSTGRESQL_ERROR_CODES } from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
|
||||
import { formatWithArray } from 'node-pg-format';
|
||||
|
||||
export interface FetchTableOptions extends MutationOrQueryBaseOptions {
|
||||
/**
|
||||
@@ -30,6 +30,12 @@ export interface FetchTableOptions extends MutationOrQueryBaseOptions {
|
||||
* Determines whether the query should fetch the rows or not.
|
||||
*/
|
||||
preventRowFetching?: boolean;
|
||||
/**
|
||||
* Filtering configuration.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
filters?: DataGridFilter[];
|
||||
}
|
||||
|
||||
export interface FetchTableReturnType {
|
||||
@@ -37,18 +43,10 @@ export interface FetchTableReturnType {
|
||||
* List of columns in the table.
|
||||
*/
|
||||
columns: NormalizedQueryDataRow[];
|
||||
/**
|
||||
* List of rows in the table.
|
||||
*/
|
||||
rows: NormalizedQueryDataRow[];
|
||||
/**
|
||||
* Foreign key relations in the table.
|
||||
*/
|
||||
foreignKeyRelations: ForeignKeyRelation[];
|
||||
/**
|
||||
* Total number of rows in the table.
|
||||
*/
|
||||
numberOfRows: number;
|
||||
/**
|
||||
* Response metadata that usually contains information about the schema and
|
||||
* the table for which the query was run.
|
||||
@@ -68,43 +66,7 @@ export default async function fetchTable({
|
||||
table,
|
||||
appUrl,
|
||||
adminSecret,
|
||||
limit,
|
||||
offset,
|
||||
orderBy,
|
||||
preventRowFetching,
|
||||
}: FetchTableOptions): Promise<FetchTableReturnType> {
|
||||
let limitAndOffsetClause = '';
|
||||
|
||||
if (preventRowFetching) {
|
||||
limitAndOffsetClause = `LIMIT 0`;
|
||||
} else if (limit && offset) {
|
||||
limitAndOffsetClause = `LIMIT ${limit} OFFSET ${offset}`;
|
||||
} else if (limit) {
|
||||
limitAndOffsetClause = `LIMIT ${limit}`;
|
||||
}
|
||||
|
||||
let orderByClause = 'ORDER BY 1';
|
||||
|
||||
if (orderBy && orderBy.length > 0) {
|
||||
// Note: This part will be added to the SQL template
|
||||
const pgFormatTemplate = orderBy.map(() => '%I %s').join(' ');
|
||||
|
||||
// Note: We are flattening object values so that we can pass them to the
|
||||
// formatter function as arguments
|
||||
const flattenedOrderByValues = orderBy.reduce<OrderBy[]>(
|
||||
(values, currentOrderBy) => {
|
||||
const currentValues = Object.values(currentOrderBy) as OrderBy[];
|
||||
return [...values, ...currentValues];
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
orderByClause = formatWithArray(
|
||||
`ORDER BY ${pgFormatTemplate}`,
|
||||
flattenedOrderByValues,
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(`${appUrl}/v2/query`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -153,14 +115,6 @@ export default async function fetchTable({
|
||||
schema,
|
||||
table,
|
||||
),
|
||||
getPreparedReadOnlyHasuraQuery(
|
||||
dataSource,
|
||||
`SELECT ROW_TO_JSON(TABLE_DATA) FROM (SELECT * FROM %I.%I %s %s) TABLE_DATA`,
|
||||
schema,
|
||||
table,
|
||||
orderByClause,
|
||||
limitAndOffsetClause,
|
||||
),
|
||||
getPreparedReadOnlyHasuraQuery(
|
||||
dataSource,
|
||||
`SELECT ROW_TO_JSON(TABLE_DATA) FROM (\
|
||||
@@ -178,12 +132,6 @@ export default async function fetchTable({
|
||||
schema,
|
||||
table,
|
||||
),
|
||||
getPreparedReadOnlyHasuraQuery(
|
||||
dataSource,
|
||||
`SELECT COUNT(*) FROM %I.%I`,
|
||||
schema,
|
||||
table,
|
||||
),
|
||||
],
|
||||
type: 'bulk',
|
||||
version: 1,
|
||||
@@ -207,8 +155,6 @@ export default async function fetchTable({
|
||||
if (schemaNotFound || tableNotFound) {
|
||||
return {
|
||||
columns: [],
|
||||
rows: [],
|
||||
numberOfRows: 0,
|
||||
foreignKeyRelations: [],
|
||||
metadata: { schema, table, schemaNotFound, tableNotFound },
|
||||
};
|
||||
@@ -220,8 +166,6 @@ export default async function fetchTable({
|
||||
) {
|
||||
return {
|
||||
columns: [],
|
||||
rows: [],
|
||||
numberOfRows: 0,
|
||||
foreignKeyRelations: [],
|
||||
metadata: { schema, table, columnsNotFound: true },
|
||||
};
|
||||
@@ -237,9 +181,7 @@ export default async function fetchTable({
|
||||
}
|
||||
|
||||
const [, ...rawColumns] = responseData[0].result;
|
||||
const [, ...rawData] = responseData[1].result;
|
||||
const [, ...rawConstraints] = responseData[2].result;
|
||||
const [, ...[rawAggregate]] = responseData[3].result;
|
||||
const [, ...rawConstraints] = responseData[1].result;
|
||||
|
||||
const foreignKeyRelationMap = new Map<string, string>();
|
||||
const uniqueKeyConstraintMap = new Map<string, string[]>();
|
||||
@@ -323,13 +265,8 @@ export default async function fetchTable({
|
||||
} as NormalizedQueryDataRow;
|
||||
})
|
||||
.sort((a, b) => a.ordinal_position - b.ordinal_position);
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows: rawData.map((rawRow) =>
|
||||
JSON.parse(rawRow),
|
||||
) as NormalizedQueryDataRow[],
|
||||
foreignKeyRelations: flatForeignKeyRelations,
|
||||
numberOfRows: rawAggregate ? parseInt(rawAggregate, 10) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { DataGridFilter } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import type {
|
||||
MutationOrQueryBaseOptions,
|
||||
NormalizedQueryDataRow,
|
||||
OrderBy,
|
||||
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||
import { getPreparedReadOnlyHasuraQuery } from '@/features/orgs/projects/database/dataGrid/utils/hasuraQueryHelpers';
|
||||
import { isNotEmptyValue } from '@/lib/utils';
|
||||
|
||||
export interface FetchTableRowsOptions extends MutationOrQueryBaseOptions {
|
||||
/**
|
||||
* Name of the columns to fetch
|
||||
*/
|
||||
columnNames: string[];
|
||||
/**
|
||||
* Limit of rows to fetch.
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* Offset of rows to fetch.
|
||||
*/
|
||||
offset: number;
|
||||
/**
|
||||
* Ordering configuration.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
orderBy: OrderBy[];
|
||||
/**
|
||||
* Filtering configuration.
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
filters: DataGridFilter[];
|
||||
}
|
||||
|
||||
export type FetchTableRowsResult = {
|
||||
error?: string | null;
|
||||
rows: NormalizedQueryDataRow[];
|
||||
numberOfRows: number;
|
||||
};
|
||||
|
||||
function createRowQuery({
|
||||
columnNames,
|
||||
limit,
|
||||
offset,
|
||||
orderBy,
|
||||
filters,
|
||||
schema,
|
||||
table,
|
||||
dataSource,
|
||||
}: FetchTableRowsOptions) {
|
||||
return {
|
||||
type: 'select',
|
||||
args: {
|
||||
source: dataSource,
|
||||
table: { schema, name: table },
|
||||
columns: columnNames,
|
||||
// TODO: create function
|
||||
where: {
|
||||
$and: filters?.map(({ column, op, value }) => ({
|
||||
[column]: {
|
||||
[op]: op === '$in' || op === '$nin' ? JSON.parse(value) : value,
|
||||
},
|
||||
})),
|
||||
},
|
||||
offset,
|
||||
limit,
|
||||
order_by:
|
||||
orderBy?.map((ob) => ({
|
||||
column: ob.columnName,
|
||||
type: ob.mode.toLocaleLowerCase(),
|
||||
})) ?? [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchTableRows({
|
||||
columnNames,
|
||||
limit,
|
||||
offset,
|
||||
orderBy,
|
||||
filters,
|
||||
adminSecret,
|
||||
dataSource,
|
||||
appUrl,
|
||||
table,
|
||||
schema,
|
||||
}: FetchTableRowsOptions): Promise<FetchTableRowsResult> {
|
||||
const body = {
|
||||
type: 'bulk',
|
||||
args: [
|
||||
createRowQuery({
|
||||
columnNames,
|
||||
limit,
|
||||
offset,
|
||||
orderBy,
|
||||
filters,
|
||||
dataSource,
|
||||
table,
|
||||
schema,
|
||||
appUrl,
|
||||
adminSecret,
|
||||
}),
|
||||
getPreparedReadOnlyHasuraQuery(
|
||||
dataSource,
|
||||
`SELECT COUNT(*) FROM %I.%I`,
|
||||
schema,
|
||||
table,
|
||||
),
|
||||
],
|
||||
};
|
||||
const response = await fetch(`${appUrl}/v2/query`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-hasura-admin-secret': adminSecret,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const responseBody = await response.json();
|
||||
|
||||
if (isNotEmptyValue(responseBody.error)) {
|
||||
return {
|
||||
rows: [],
|
||||
error: responseBody.error,
|
||||
numberOfRows: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const [
|
||||
rows,
|
||||
{
|
||||
result: [, [maxNumberOfRows]],
|
||||
},
|
||||
] = responseBody;
|
||||
|
||||
return {
|
||||
rows,
|
||||
error: null,
|
||||
numberOfRows: isNotEmptyValue(filters)
|
||||
? rows.length
|
||||
: Number(maxNumberOfRows),
|
||||
};
|
||||
}
|
||||
|
||||
export default fetchTableRows;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useTableRows } from './useTableRows';
|
||||
@@ -0,0 +1,66 @@
|
||||
import { generateAppServiceUrl } from '@/features/orgs/projects/common/utils/generateAppServiceUrl';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { isNotEmptyValue } from '@/lib/utils';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import {
|
||||
useQuery,
|
||||
type QueryKey,
|
||||
type UseQueryOptions,
|
||||
} from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import type {
|
||||
FetchTableRowsOptions,
|
||||
FetchTableRowsResult,
|
||||
} from './fetchTableRows';
|
||||
import fetchTableRows from './fetchTableRows';
|
||||
|
||||
export interface UseTableRowsQueryOptions
|
||||
extends Pick<
|
||||
FetchTableRowsOptions,
|
||||
'limit' | 'filters' | 'offset' | 'orderBy' | 'columnNames'
|
||||
> {
|
||||
/**
|
||||
* Props passed to the underlying query hook.
|
||||
*/
|
||||
queryOptions?: UseQueryOptions;
|
||||
}
|
||||
|
||||
function useTableRows(
|
||||
queryKey: QueryKey,
|
||||
{ queryOptions, ...options }: UseTableRowsQueryOptions,
|
||||
) {
|
||||
const { project } = useProject();
|
||||
const {
|
||||
query: { dataSourceSlug, schemaSlug, tableSlug },
|
||||
isReady,
|
||||
} = useRouter();
|
||||
|
||||
const dependenciesLoaded =
|
||||
isNotEmptyValue(project) && isNotEmptyValue(options.columnNames) && isReady;
|
||||
return useQuery<FetchTableRowsResult>(queryKey, {
|
||||
queryFn: () => {
|
||||
const appUrl = isNotEmptyValue(project)
|
||||
? generateAppServiceUrl(project!.subdomain, project!.region, 'hasura')
|
||||
: '';
|
||||
return fetchTableRows({
|
||||
appUrl,
|
||||
dataSource: dataSourceSlug as string,
|
||||
schema: schemaSlug as string,
|
||||
table: tableSlug as string,
|
||||
adminSecret:
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||
? getHasuraAdminSecret()
|
||||
: project!.config!.hasura.adminSecret,
|
||||
...options,
|
||||
});
|
||||
},
|
||||
retry: false,
|
||||
keepPreviousData: true,
|
||||
...(queryOptions && { queryOptions }),
|
||||
enabled: isNotEmptyValue(queryOptions?.enabled)
|
||||
? queryOptions.enabled && dependenciesLoaded
|
||||
: dependenciesLoaded,
|
||||
});
|
||||
}
|
||||
|
||||
export default useTableRows;
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { DataGridFilter } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { isEmptyValue } from '@/lib/utils';
|
||||
|
||||
export const DATA_GRID_FILTER_STORAGE_KEY = 'nhost_data_grid_filter_storage';
|
||||
|
||||
class PersistenDataGrdiFilterStorage {
|
||||
private static getAllStoredData(): Record<string, DataGridFilter[]> {
|
||||
const storedData = localStorage.getItem(DATA_GRID_FILTER_STORAGE_KEY);
|
||||
if (isEmptyValue(storedData)) {
|
||||
return {};
|
||||
}
|
||||
const allStoredData = JSON.parse(storedData as string);
|
||||
|
||||
return allStoredData;
|
||||
}
|
||||
|
||||
static getDataGridFilters(tablePath: string): DataGridFilter[] {
|
||||
const allStoredData = PersistenDataGrdiFilterStorage.getAllStoredData();
|
||||
return allStoredData[tablePath] ?? [];
|
||||
}
|
||||
|
||||
static saveDataGridFilters(tablePath: string, filters: DataGridFilter[]) {
|
||||
const allStoredData = PersistenDataGrdiFilterStorage.getAllStoredData();
|
||||
|
||||
const updatedAllStoredData: Record<string, DataGridFilter[]> = {
|
||||
...allStoredData,
|
||||
[tablePath]: filters,
|
||||
};
|
||||
|
||||
localStorage.setItem(
|
||||
DATA_GRID_FILTER_STORAGE_KEY,
|
||||
JSON.stringify(updatedAllStoredData),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PersistenDataGrdiFilterStorage;
|
||||
@@ -67,6 +67,8 @@ export function getPreparedReadOnlyHasuraQuery(
|
||||
...args,
|
||||
);
|
||||
|
||||
// console.log({ preparedHasuraQuery });
|
||||
|
||||
return {
|
||||
...preparedHasuraQuery,
|
||||
args: {
|
||||
|
||||
@@ -21,23 +21,22 @@ import { ReplicasFormSection } from '@/features/orgs/projects/services/component
|
||||
import { StorageFormSection } from '@/features/orgs/projects/services/components/ServiceForm/components/StorageFormSection';
|
||||
|
||||
import {
|
||||
defaultServiceFormValues,
|
||||
validationSchema,
|
||||
type Port,
|
||||
type ServiceFormProps,
|
||||
type ServiceFormValues,
|
||||
} from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
|
||||
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { getFormattedServiceConfig } from '@/features/orgs/projects/services/utils/getFormattedServiceConfig';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useInsertRunServiceConfigMutation,
|
||||
useReplaceRunServiceConfigMutation,
|
||||
type ConfigRunServiceConfigInsertInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { removeTypename } from '@/utils/helpers';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -69,14 +68,7 @@ export default function ServiceForm({
|
||||
useState<Error | null>(null);
|
||||
|
||||
const form = useForm<ServiceFormValues>({
|
||||
defaultValues: initialData ?? {
|
||||
compute: {
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
replicas: 1,
|
||||
autoscaler: null,
|
||||
},
|
||||
defaultValues: initialData ?? defaultServiceFormValues,
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
@@ -142,66 +134,8 @@ export default function ServiceForm({
|
||||
onDirtyStateChange(isDirty, location);
|
||||
}, [isDirty, location, onDirtyStateChange]);
|
||||
|
||||
const getFormattedConfig = (values: ServiceFormValues) => {
|
||||
// Remove any __typename property from the values
|
||||
const sanitizedValues = removeTypename(values) as ServiceFormValues;
|
||||
const sanitizedInitialDataPorts: Port[] = initialData?.ports
|
||||
? removeTypename(initialData.ports)
|
||||
: [];
|
||||
|
||||
const config: ConfigRunServiceConfigInsertInput = {
|
||||
name: sanitizedValues.name,
|
||||
image: {
|
||||
image: sanitizedValues.image,
|
||||
pullCredentials: sanitizedValues.pullCredentials,
|
||||
},
|
||||
command: sanitizedValues.command?.map((arg) => arg.argument),
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.compute?.cpu,
|
||||
memory: sanitizedValues.compute?.memory,
|
||||
},
|
||||
storage: sanitizedValues.storage?.map((item) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
capacity: item.capacity,
|
||||
})),
|
||||
replicas: sanitizedValues.replicas,
|
||||
autoscaler: sanitizedValues.autoscaler
|
||||
? {
|
||||
maxReplicas: sanitizedValues.autoscaler?.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
environment: sanitizedValues.environment?.map((item) => ({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
})),
|
||||
ports: sanitizedValues.ports?.map((item) => ({
|
||||
port: item.port,
|
||||
type: item.type,
|
||||
publish: item.publish,
|
||||
ingresses: item.ingresses as any, // cannot be changed on the UI always null type checking can be skipped.
|
||||
rateLimit:
|
||||
sanitizedInitialDataPorts.find(
|
||||
(port) => port.port === item.port && port.type === item.type,
|
||||
)?.rateLimit ?? (null as any), // cannot be changed on the UI always null type checking can be skipped.
|
||||
})),
|
||||
healthCheck: sanitizedValues.healthCheck
|
||||
? {
|
||||
port: sanitizedValues.healthCheck?.port,
|
||||
initialDelaySeconds:
|
||||
sanitizedValues.healthCheck?.initialDelaySeconds,
|
||||
probePeriodSeconds: sanitizedValues.healthCheck?.probePeriodSeconds,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
const createOrUpdateService = async (values: ServiceFormValues) => {
|
||||
const config = getFormattedConfig(values);
|
||||
const config = getFormattedServiceConfig({ values, initialData });
|
||||
|
||||
if (serviceID) {
|
||||
// Update service config
|
||||
@@ -292,7 +226,10 @@ export default function ServiceForm({
|
||||
};
|
||||
|
||||
const copyConfig = () => {
|
||||
const config = getFormattedConfig(formValues);
|
||||
const config = getFormattedServiceConfig({
|
||||
values: formValues,
|
||||
initialData,
|
||||
});
|
||||
|
||||
const base64Config = btoa(JSON.stringify(config));
|
||||
|
||||
|
||||
@@ -87,6 +87,15 @@ export type ServiceFormInitialData = Omit<ServiceFormValues, 'ports'> & {
|
||||
}[];
|
||||
};
|
||||
|
||||
export const defaultServiceFormValues = {
|
||||
compute: {
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
replicas: 1,
|
||||
autoscaler: null,
|
||||
};
|
||||
|
||||
export interface ServiceFormProps extends DialogFormProps {
|
||||
/**
|
||||
* To use in conjunction with initialData to allow for updating the service
|
||||
|
||||
@@ -15,7 +15,10 @@ import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatfo
|
||||
import { type RunService } from '@/features/orgs/projects/common/hooks/useRunServices';
|
||||
import { ServiceForm } from '@/features/orgs/projects/services/components/ServiceForm';
|
||||
import { type PortTypes } from '@/features/orgs/projects/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import type { ServiceFormInitialData } from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
import {
|
||||
defaultServiceFormValues,
|
||||
type ServiceFormInitialData,
|
||||
} from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
@@ -74,12 +77,15 @@ export default function ServicesList({
|
||||
ingresses: item.ingresses,
|
||||
rateLimit: item.rateLimit,
|
||||
})),
|
||||
compute: service.config?.resources?.compute ?? {
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
replicas: service.config?.resources?.replicas,
|
||||
autoscaler: service?.config?.resources?.autoscaler,
|
||||
compute:
|
||||
service.config?.resources?.compute ??
|
||||
defaultServiceFormValues.compute,
|
||||
replicas:
|
||||
service.config?.resources?.replicas ??
|
||||
defaultServiceFormValues.replicas,
|
||||
autoscaler:
|
||||
service?.config?.resources?.autoscaler ??
|
||||
defaultServiceFormValues.autoscaler,
|
||||
storage: service.config?.resources?.storage,
|
||||
} as ServiceFormInitialData
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { PortTypes } from '@/features/orgs/projects/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import getFormattedServiceConfig from './getFormattedServiceConfig';
|
||||
|
||||
describe('getFormattedServiceConfig', () => {
|
||||
it('pghero config should be formatted correctly', () => {
|
||||
const pgheroFormValues = {
|
||||
name: 'pghero',
|
||||
image: 'docker.io/ankane/pghero:latest',
|
||||
command: [],
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
storage: [],
|
||||
replicas: 1,
|
||||
},
|
||||
environment: [
|
||||
{
|
||||
name: 'DATABASE_URL',
|
||||
value:
|
||||
'postgres://postgres:[PASSWORD]@postgres-service:5432/[SUBDOMAIN]?sslmode=disable',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_USERNAME',
|
||||
value: '[USER]',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_PASSWORD',
|
||||
value: '[PASSWORD]',
|
||||
},
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
port: 8080,
|
||||
type: PortTypes.HTTP,
|
||||
publish: true,
|
||||
},
|
||||
],
|
||||
autoscaler: null,
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
replicas: 1,
|
||||
storage: [],
|
||||
};
|
||||
|
||||
const formattedConfig = getFormattedServiceConfig({
|
||||
values: pgheroFormValues,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
name: 'pghero',
|
||||
image: {
|
||||
image: 'docker.io/ankane/pghero:latest',
|
||||
},
|
||||
command: [],
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
storage: [],
|
||||
replicas: 1,
|
||||
autoscaler: null,
|
||||
},
|
||||
environment: [
|
||||
{
|
||||
name: 'DATABASE_URL',
|
||||
value:
|
||||
'postgres://postgres:[PASSWORD]@postgres-service:5432/[SUBDOMAIN]?sslmode=disable',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_USERNAME',
|
||||
value: '[USER]',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_PASSWORD',
|
||||
value: '[PASSWORD]',
|
||||
},
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
port: 8080,
|
||||
type: 'http',
|
||||
publish: true,
|
||||
rateLimit: null,
|
||||
},
|
||||
],
|
||||
healthCheck: null,
|
||||
};
|
||||
|
||||
expect(formattedConfig).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
Port,
|
||||
ServiceFormInitialData,
|
||||
ServiceFormValues,
|
||||
} from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
import type { ConfigRunServiceConfigInsertInput } from '@/utils/__generated__/graphql';
|
||||
import { removeTypename } from '@/utils/helpers';
|
||||
|
||||
export interface GetFormattedServiceConfigProps {
|
||||
values: ServiceFormValues;
|
||||
initialData?: ServiceFormInitialData;
|
||||
}
|
||||
|
||||
export default function getFormattedServiceConfig({
|
||||
values,
|
||||
initialData,
|
||||
}: GetFormattedServiceConfigProps) {
|
||||
// Remove any __typename property from the values
|
||||
const sanitizedValues = removeTypename(values) as ServiceFormValues;
|
||||
const sanitizedInitialDataPorts: Port[] = initialData?.ports
|
||||
? removeTypename(initialData.ports)
|
||||
: [];
|
||||
|
||||
const config: ConfigRunServiceConfigInsertInput = {
|
||||
name: sanitizedValues.name,
|
||||
image: {
|
||||
image: sanitizedValues.image,
|
||||
pullCredentials: sanitizedValues.pullCredentials,
|
||||
},
|
||||
command: sanitizedValues.command?.map((arg) => arg.argument),
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.compute?.cpu,
|
||||
memory: sanitizedValues.compute?.memory,
|
||||
},
|
||||
storage: sanitizedValues.storage?.map((item) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
capacity: item.capacity,
|
||||
})),
|
||||
replicas: sanitizedValues.replicas,
|
||||
autoscaler: sanitizedValues.autoscaler
|
||||
? {
|
||||
maxReplicas: sanitizedValues.autoscaler?.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
environment: sanitizedValues.environment?.map((item) => ({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
})),
|
||||
ports: sanitizedValues.ports?.map((item) => ({
|
||||
port: item.port,
|
||||
type: item.type,
|
||||
publish: item.publish,
|
||||
ingresses: item.ingresses as any, // cannot be changed on the UI always null type checking can be skipped.
|
||||
rateLimit:
|
||||
sanitizedInitialDataPorts.find(
|
||||
(port) => port.port === item.port && port.type === item.type,
|
||||
)?.rateLimit ?? (null as any), // cannot be changed on the UI always null type checking can be skipped.
|
||||
})),
|
||||
healthCheck: sanitizedValues.healthCheck
|
||||
? {
|
||||
port: sanitizedValues.healthCheck?.port,
|
||||
initialDelaySeconds: sanitizedValues.healthCheck?.initialDelaySeconds,
|
||||
probePeriodSeconds: sanitizedValues.healthCheck?.probePeriodSeconds,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as getFormattedServiceConfig } from './getFormattedServiceConfig';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as parseConfigFromInstallLink } from './parseConfigFromInstallLink';
|
||||
@@ -0,0 +1,156 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import parseConfigFromInstallLink from './parseConfigFromInstallLink';
|
||||
|
||||
describe('parseConfigFromInstallLink', () => {
|
||||
it('pghero config without autoscaler should be formatted correctly', () => {
|
||||
const pgheroBase64Config =
|
||||
'eyJuYW1lIjoicGdoZXJvIiwiaW1hZ2UiOnsiaW1hZ2UiOiJkb2NrZXIuaW8vYW5rYW5lL3BnaGVybzpsYXRlc3QifSwiY29tbWFuZCI6W10sInJlc291cmNlcyI6eyJjb21wdXRlIjp7ImNwdSI6MTI1LCJtZW1vcnkiOjI1Nn0sInN0b3JhZ2UiOltdLCJyZXBsaWNhcyI6MX0sImVudmlyb25tZW50IjpbeyJuYW1lIjoiREFUQUJBU0VfVVJMIiwidmFsdWUiOiJwb3N0Z3JlczovL3Bvc3RncmVzOltQQVNTV09SRF1AcG9zdGdyZXMtc2VydmljZTo1NDMyL1tTVUJET01BSU5dP3NzbG1vZGU9ZGlzYWJsZSJ9LHsibmFtZSI6IlBHSEVST19VU0VSTkFNRSIsInZhbHVlIjoiW1VTRVJdIn0seyJuYW1lIjoiUEdIRVJPX1BBU1NXT1JEIiwidmFsdWUiOiJbUEFTU1dPUkRdIn1dLCJwb3J0cyI6W3sicG9ydCI6ODA4MCwidHlwZSI6Imh0dHAiLCJwdWJsaXNoIjp0cnVlfV19';
|
||||
|
||||
const config = parseConfigFromInstallLink(pgheroBase64Config);
|
||||
|
||||
const expected = {
|
||||
name: 'pghero',
|
||||
image: 'docker.io/ankane/pghero:latest',
|
||||
command: [],
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
storage: [],
|
||||
replicas: 1,
|
||||
},
|
||||
environment: [
|
||||
{
|
||||
name: 'DATABASE_URL',
|
||||
value:
|
||||
'postgres://postgres:[PASSWORD]@postgres-service:5432/[SUBDOMAIN]?sslmode=disable',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_USERNAME',
|
||||
value: '[USER]',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_PASSWORD',
|
||||
value: '[PASSWORD]',
|
||||
},
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
port: 8080,
|
||||
type: 'http',
|
||||
publish: true,
|
||||
},
|
||||
],
|
||||
autoscaler: null,
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
replicas: 1,
|
||||
storage: [],
|
||||
};
|
||||
|
||||
expect(config).toEqual(expected);
|
||||
});
|
||||
|
||||
it('antivirus config without autoscaler should be formatted correctly', () => {
|
||||
const antivirusBase64Config =
|
||||
'eyJuYW1lIjoiY2xhbWF2IiwiaW1hZ2UiOnsiaW1hZ2UiOiJkb2NrZXIuaW8vbmhvc3QvY2xhbWF2OjAuMS4xIn0sImNvbW1hbmQiOltdLCJyZXNvdXJjZXMiOnsiY29tcHV0ZSI6eyJjcHUiOjEwMDAsIm1lbW9yeSI6MjA0OH0sInN0b3JhZ2UiOltdLCJyZXBsaWNhcyI6MX0sImVudmlyb25tZW50IjpbXSwicG9ydHMiOlt7InBvcnQiOiIzMzEwIiwidHlwZSI6InRjcCIsInB1Ymxpc2giOmZhbHNlfV19';
|
||||
|
||||
const config = parseConfigFromInstallLink(antivirusBase64Config);
|
||||
|
||||
const expected = {
|
||||
name: 'clamav',
|
||||
image: 'docker.io/nhost/clamav:0.1.1',
|
||||
command: [],
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: 1000,
|
||||
memory: 2048,
|
||||
},
|
||||
storage: [],
|
||||
replicas: 1,
|
||||
},
|
||||
environment: [],
|
||||
ports: [
|
||||
{
|
||||
port: '3310',
|
||||
type: 'tcp',
|
||||
publish: false,
|
||||
},
|
||||
],
|
||||
autoscaler: null,
|
||||
compute: {
|
||||
cpu: 1000,
|
||||
memory: 2048,
|
||||
},
|
||||
replicas: 1,
|
||||
storage: [],
|
||||
};
|
||||
expect(config).toEqual(expected);
|
||||
});
|
||||
|
||||
it('invalid config should throw an error', () => {
|
||||
const invalidBase64Config = 'invalid';
|
||||
|
||||
expect(() => parseConfigFromInstallLink(invalidBase64Config)).toThrow();
|
||||
});
|
||||
|
||||
it('pghero config with autoscaler should be formatted correctly', () => {
|
||||
const pgheroWithAutoscalerBase64 =
|
||||
'eyJuYW1lIjoicGdoZXJvIiwiaW1hZ2UiOnsiaW1hZ2UiOiJkb2NrZXIuaW8vYW5rYW5lL3BnaGVybzpsYXRlc3QifSwiY29tbWFuZCI6W10sInJlc291cmNlcyI6eyJjb21wdXRlIjp7ImNwdSI6MTI1LCJtZW1vcnkiOjI1Nn0sInN0b3JhZ2UiOltdLCJyZXBsaWNhcyI6MSwiYXV0b3NjYWxlciI6eyJtYXhSZXBsaWNhcyI6MTF9fSwiZW52aXJvbm1lbnQiOlt7Im5hbWUiOiJEQVRBQkFTRV9VUkwiLCJ2YWx1ZSI6InBvc3RncmVzOi8vcG9zdGdyZXM6W1BBU1NXT1JEXUBwb3N0Z3Jlcy1zZXJ2aWNlOjU0MzIvW1NVQkRPTUFJTl0/c3NsbW9kZT1kaXNhYmxlIn0seyJuYW1lIjoiUEdIRVJPX1VTRVJOQU1FIiwidmFsdWUiOiJbVVNFUl0ifSx7Im5hbWUiOiJQR0hFUk9fUEFTU1dPUkQiLCJ2YWx1ZSI6IltQQVNTV09SRF0ifV0sInBvcnRzIjpbeyJwb3J0Ijo4MDgwLCJ0eXBlIjoiaHR0cCIsInB1Ymxpc2giOnRydWUsInJhdGVMaW1pdCI6bnVsbH1dLCJoZWFsdGhDaGVjayI6bnVsbH0=';
|
||||
|
||||
const config = parseConfigFromInstallLink(pgheroWithAutoscalerBase64);
|
||||
|
||||
const expected = {
|
||||
name: 'pghero',
|
||||
image: 'docker.io/ankane/pghero:latest',
|
||||
command: [],
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
storage: [],
|
||||
replicas: 1,
|
||||
autoscaler: {
|
||||
maxReplicas: 11,
|
||||
},
|
||||
},
|
||||
environment: [
|
||||
{
|
||||
name: 'DATABASE_URL',
|
||||
value:
|
||||
'postgres://postgres:[PASSWORD]@postgres-service:5432/[SUBDOMAIN]?sslmode=disable',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_USERNAME',
|
||||
value: '[USER]',
|
||||
},
|
||||
{
|
||||
name: 'PGHERO_PASSWORD',
|
||||
value: '[PASSWORD]',
|
||||
},
|
||||
],
|
||||
ports: [
|
||||
{
|
||||
port: 8080,
|
||||
type: 'http',
|
||||
publish: true,
|
||||
},
|
||||
],
|
||||
autoscaler: {
|
||||
maxReplicas: 11,
|
||||
},
|
||||
compute: {
|
||||
cpu: 125,
|
||||
memory: 256,
|
||||
},
|
||||
replicas: 1,
|
||||
storage: [],
|
||||
};
|
||||
|
||||
expect(config).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { type RunServiceConfig } from '@/features/orgs/projects/common/hooks/useRunServices';
|
||||
import { type PortTypes } from '@/features/orgs/projects/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import {
|
||||
defaultServiceFormValues,
|
||||
type ServiceFormInitialData,
|
||||
} from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
|
||||
export default function parseConfigFromInstallLink(
|
||||
base64Config: string,
|
||||
): ServiceFormInitialData {
|
||||
const decodedConfig = atob(base64Config);
|
||||
const parsedConfig: RunServiceConfig = JSON.parse(decodedConfig);
|
||||
const initialData = {
|
||||
...parsedConfig,
|
||||
autoscaler:
|
||||
parsedConfig?.resources?.autoscaler ??
|
||||
defaultServiceFormValues.autoscaler,
|
||||
compute:
|
||||
parsedConfig?.resources?.compute ?? defaultServiceFormValues.compute,
|
||||
image: parsedConfig?.image?.image,
|
||||
command: parsedConfig?.command?.map((arg) => ({
|
||||
argument: arg,
|
||||
})),
|
||||
environment:
|
||||
parsedConfig?.environment?.map((env) => ({
|
||||
name: env.name,
|
||||
value: env.value,
|
||||
})) ?? undefined,
|
||||
healthCheck: parsedConfig?.healthCheck
|
||||
? {
|
||||
port: parsedConfig.healthCheck.port ?? 3000,
|
||||
initialDelaySeconds:
|
||||
parsedConfig.healthCheck.initialDelaySeconds ?? 30,
|
||||
probePeriodSeconds: parsedConfig.healthCheck.probePeriodSeconds ?? 60,
|
||||
}
|
||||
: undefined,
|
||||
ports:
|
||||
parsedConfig?.ports?.map((item) => ({
|
||||
port: item.port ?? 3000,
|
||||
type: item.type as PortTypes,
|
||||
publish: Boolean(item.publish),
|
||||
})) ?? [],
|
||||
replicas: parsedConfig?.resources?.replicas,
|
||||
storage: parsedConfig?.resources?.storage ?? undefined,
|
||||
};
|
||||
|
||||
return initialData;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import type { DataGridHeaderProps } from '@/features/orgs/projects/storage/dataG
|
||||
import { DataGridHeader } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeader';
|
||||
import { DataTableDesignProvider } from '@/features/orgs/projects/storage/dataGrid/providers/DataTableDesignProvider';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import type { ForwardedRef, ReactNode } from 'react';
|
||||
import { forwardRef, useEffect, useRef } from 'react';
|
||||
import { mergeRefs } from 'react-merge-refs';
|
||||
import type { Column, Row, SortingRule, TableOptions } from 'react-table';
|
||||
@@ -31,7 +31,7 @@ export interface DataGridProps<TColumnData extends object>
|
||||
*
|
||||
* @default null
|
||||
*/
|
||||
emptyStateMessage?: string;
|
||||
emptyStateMessage?: ReactNode;
|
||||
/**
|
||||
* Additional configuration options for the `react-table` hook.
|
||||
*/
|
||||
@@ -71,6 +71,10 @@ export interface DataGridProps<TColumnData extends object>
|
||||
* Determines whether the Grid is used for displaying files.
|
||||
*/
|
||||
isFileDataGrid?: boolean;
|
||||
/**
|
||||
* Determines whether rows are being fetched or not
|
||||
*/
|
||||
isFetching?: boolean;
|
||||
}
|
||||
|
||||
function DataGrid<TColumnData extends object>(
|
||||
@@ -89,6 +93,7 @@ function DataGrid<TColumnData extends object>(
|
||||
loading,
|
||||
className,
|
||||
isFileDataGrid,
|
||||
isFetching,
|
||||
}: DataGridProps<TColumnData>,
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) {
|
||||
@@ -151,12 +156,19 @@ function DataGrid<TColumnData extends object>(
|
||||
)}
|
||||
>
|
||||
<DataGridFrame>
|
||||
<DataGridHeader {...headerProps} />
|
||||
<DataGridBody
|
||||
isFileDataGrid={isFileDataGrid}
|
||||
emptyStateMessage={emptyStateMessage}
|
||||
loading={loading}
|
||||
/>
|
||||
<div className="relative h-full">
|
||||
<DataGridHeader {...headerProps} />
|
||||
{isFetching && (
|
||||
<div className="absolute top-0 z-50 flex h-full w-full justify-center bg-[rgba(0,0,0,.5)]">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
<DataGridBody
|
||||
isFileDataGrid={isFileDataGrid}
|
||||
emptyStateMessage={emptyStateMessage}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</DataGridFrame>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -22,9 +22,9 @@ class PersistenDataTableConfigurationStorage {
|
||||
if (isEmptyValue(storedData)) {
|
||||
return {};
|
||||
}
|
||||
const allHiddenColumns = JSON.parse(storedData as string);
|
||||
const allStoredData = JSON.parse(storedData as string);
|
||||
|
||||
return allHiddenColumns;
|
||||
return allStoredData;
|
||||
}
|
||||
|
||||
static getHiddenColumns(tablePath: string): string[] {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { OrgLayout } from '@/features/orgs/layout/OrgLayout';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import { useTablePath } from '@/features/orgs/projects/database/common/hooks/useTablePath';
|
||||
import { DataBrowserGrid } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid';
|
||||
import { DataGridFilterProvider } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserGrid/DataGridFilterProvider';
|
||||
import { DataBrowserSidebar } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserSidebar';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import type { ReactElement } from 'react';
|
||||
@@ -31,7 +32,9 @@ export default function DataBrowserTableDetailsPage() {
|
||||
|
||||
return (
|
||||
<RetryableErrorBoundary>
|
||||
<DataBrowserGrid sortBy={sortBy} onSort={handleSortByChange} />
|
||||
<DataGridFilterProvider>
|
||||
<DataBrowserGrid sortBy={sortBy} onSort={handleSortByChange} />
|
||||
</DataGridFilterProvider>
|
||||
</RetryableErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,16 +11,12 @@ import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { OrgLayout } from '@/features/orgs/layout/OrgLayout';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
useRunServices,
|
||||
type RunServiceConfig,
|
||||
} from '@/features/orgs/projects/common/hooks/useRunServices';
|
||||
import { useRunServices } from '@/features/orgs/projects/common/hooks/useRunServices';
|
||||
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { ServiceForm } from '@/features/orgs/projects/services/components/ServiceForm';
|
||||
import { type PortTypes } from '@/features/orgs/projects/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import type { ServiceFormInitialData } from '@/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { ServicesList } from '@/features/orgs/projects/services/components/ServicesList';
|
||||
import { parseConfigFromInstallLink } from '@/features/orgs/projects/services/utils/parseConfigFromInstallLink';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, type ReactElement } from 'react';
|
||||
|
||||
@@ -48,29 +44,7 @@ export default function RunPage() {
|
||||
(base64Config: string) => {
|
||||
if (router.query?.config) {
|
||||
try {
|
||||
const decodedConfig = atob(base64Config);
|
||||
const parsedConfig: RunServiceConfig = JSON.parse(decodedConfig);
|
||||
const initialData = {
|
||||
...parsedConfig,
|
||||
autoscaler: parsedConfig?.resources?.autoscaler ?? {
|
||||
maxReplicas: 0,
|
||||
},
|
||||
compute: parsedConfig?.resources?.compute ?? {
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
image: parsedConfig?.image?.image,
|
||||
command: parsedConfig?.command?.map((arg) => ({
|
||||
argument: arg,
|
||||
})),
|
||||
ports: parsedConfig?.ports?.map((item) => ({
|
||||
port: item.port,
|
||||
type: item.type as PortTypes,
|
||||
publish: item.publish,
|
||||
})),
|
||||
replicas: parsedConfig?.resources?.replicas,
|
||||
storage: parsedConfig?.resources?.storage,
|
||||
} as ServiceFormInitialData;
|
||||
const initialData = parseConfigFromInstallLink(base64Config);
|
||||
|
||||
openDrawer({
|
||||
title: (
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"icon": "at",
|
||||
"pages": [
|
||||
"products/auth/providers/overview",
|
||||
"products/auth/providers/sign-in-provider",
|
||||
"products/auth/providers/tokens",
|
||||
"products/auth/providers/connect",
|
||||
"products/auth/providers/idtokens",
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
"openapi-types": "*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ After authorizing the GitHub integration, you'll need to tell Nhost a couple of
|
||||
|
||||
### Base Directory
|
||||
|
||||
This is the folder in your repository where your Nhost folder lives. If your Nhost foder is in the root of your repository, you can leave this as `/`. If it is in a subfolder (like `/backend`), specify that path here.
|
||||
This is the folder in your repository where your Nhost folder lives. If your Nhost folder is in the root of your repository, you can leave this as `/`. If it is in a subfolder (like `/backend`), specify that path here.
|
||||
|
||||
### Deployment Branch
|
||||
|
||||
|
||||
48
docs/pnpm-lock.yaml
generated
48
docs/pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
packageExtensionsChecksum: sha256-4+NJJHoeDEOtWI2UxgTNLimXyrOojBs00S85/9Babm0=
|
||||
|
||||
importers:
|
||||
@@ -936,9 +939,6 @@ packages:
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
@@ -2106,12 +2106,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsep@1.4.0:
|
||||
@@ -3126,9 +3122,6 @@ packages:
|
||||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3599,7 +3592,7 @@ snapshots:
|
||||
ajv-errors: 3.0.0(ajv@8.17.1)
|
||||
ajv-formats: 2.1.1(ajv@8.17.1)
|
||||
avsc: 5.7.9
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
jsonpath-plus: 10.3.0
|
||||
node-fetch: 2.6.7
|
||||
transitivePeerDependencies:
|
||||
@@ -3937,7 +3930,7 @@ snapshots:
|
||||
gray-matter: 4.0.3
|
||||
ink: 6.3.1(@types/react@19.1.12)(react@19.2.0)
|
||||
inquirer: 12.9.6(@types/node@24.7.0)
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
mdast: 3.0.0
|
||||
mdast-util-mdx-jsx: 3.2.0
|
||||
react: 19.2.0
|
||||
@@ -3979,7 +3972,7 @@ snapshots:
|
||||
hast-util-to-html: 9.0.5
|
||||
hast-util-to-text: 4.0.2
|
||||
hex-rgb: 5.0.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
lodash: 4.17.21
|
||||
mdast: 3.0.0
|
||||
mdast-util-from-markdown: 2.0.2
|
||||
@@ -4094,7 +4087,7 @@ snapshots:
|
||||
favicons: 7.2.0
|
||||
fs-extra: 11.3.2
|
||||
gray-matter: 4.0.3
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
mdast: 3.0.0
|
||||
openapi-types: 12.1.3
|
||||
sharp: 0.33.5
|
||||
@@ -4131,7 +4124,7 @@ snapshots:
|
||||
ink: 6.3.1(@types/react@19.1.12)(react@19.2.0)
|
||||
ink-spinner: 5.0.0(ink@6.3.1(@types/react@19.1.12)(react@19.2.0))(react@19.2.0)
|
||||
is-online: 10.0.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
mdast: 3.0.0
|
||||
openapi-types: 12.1.3
|
||||
react: 19.2.0
|
||||
@@ -4161,7 +4154,7 @@ snapshots:
|
||||
'@mintlify/openapi-parser': 0.0.8
|
||||
fs-extra: 11.3.2
|
||||
hast-util-to-mdast: 10.1.2
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
mdast-util-mdx-jsx: 3.2.0
|
||||
neotraverse: 0.6.18
|
||||
openapi-types: 12.1.3
|
||||
@@ -4196,7 +4189,7 @@ snapshots:
|
||||
'@mintlify/mdx': 3.0.0(@radix-ui/react-popover@1.1.15(@types/react@19.1.12)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.1.12)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.2)
|
||||
'@mintlify/models': 0.0.233
|
||||
arktype: 2.1.22
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
lcm: 0.0.3
|
||||
lodash: 4.17.21
|
||||
object-hash: 3.0.0
|
||||
@@ -4791,10 +4784,6 @@ snapshots:
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
@@ -5095,7 +5084,7 @@ snapshots:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
optionalDependencies:
|
||||
typescript: 5.9.2
|
||||
@@ -5703,7 +5692,7 @@ snapshots:
|
||||
|
||||
gray-matter@4.0.3:
|
||||
dependencies:
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
kind-of: 6.0.3
|
||||
section-matter: 1.0.0
|
||||
strip-bom-string: 1.0.0
|
||||
@@ -6185,12 +6174,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -7673,8 +7657,6 @@ snapshots:
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
@@ -86,14 +86,4 @@ icon: apple
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'apple'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Apple as an OAuth provider in Nhost, you can sign in users using the Apple provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -36,14 +36,4 @@ Find the Redirect URL in your project settings -> Sign In Methods after enabling
|
||||
|
||||
## User Sign-In
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'azuread'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Azure AD as an OAuth provider in Nhost, you can sign in users using the Azure AD provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -39,13 +39,4 @@ Once saved, Bitbucket will show you a **Key (Client ID)** and a **Secret (Client
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users with Bitbucket:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: "bitbucket",
|
||||
});
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Bitbucket as an OAuth provider in Nhost, you can sign in users using the Bitbucket provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -34,14 +34,4 @@ icon: discord
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'discord'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Discord as an OAuth provider in Nhost, you can sign in users using the Discord provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -34,14 +34,4 @@ Find the Redirect URL in your project settings -> Sign In Methods after enabling
|
||||
|
||||
## User Sign-In
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'azuread'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Entra ID as an OAuth provider in Nhost, you can sign in users using the Entra ID provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -57,14 +57,4 @@ To make sure we can fetch all user data (email, profile picture and name). For t
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'facebook'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Facebook as an OAuth provider in Nhost, you can sign in users using the Facebook provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -42,14 +42,4 @@ icon: github
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: "github",
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured GitHub as an OAuth provider in Nhost, you can sign in users using the GitHub provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -34,14 +34,4 @@ icon: gitlab
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: "gitlab",
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured GitLab as an OAuth provider in Nhost, you can sign in users using the GitLab provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -66,14 +66,4 @@ icon: google
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the Nhost JavaScript client to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'google'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Google as an OAuth provider in Nhost, you can sign in users using the Google provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -57,14 +57,4 @@ icon: linkedin
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'linkedin'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured LinkedIn as an OAuth provider in Nhost, you can sign in users using the LinkedIn provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
244
docs/products/auth/providers/sign-in-provider.mdx
Normal file
244
docs/products/auth/providers/sign-in-provider.mdx
Normal file
@@ -0,0 +1,244 @@
|
||||
---
|
||||
title: Sign In with OAuth Providers
|
||||
description: Learn how OAuth provider sign-in works in Nhost and how to implement it in your application.
|
||||
sidebarTitle: Sign In
|
||||
icon: user
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Nhost supports OAuth 2.0 authentication with various social providers including GitHub, Google, Apple, Discord, and more. This guide explains the OAuth sign-in flow and how to implement it in your application.
|
||||
|
||||
## OAuth Sign-In Flow
|
||||
|
||||
The OAuth authentication flow in Nhost involves several steps coordinating between your client application, Nhost Auth service, and the OAuth provider:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Client Application
|
||||
participant NhostAuth as Nhost Auth Service
|
||||
participant Provider as OAuth Provider
|
||||
|
||||
Client->>Client: User clicks "Sign in with Provider"
|
||||
Client->>Client: Call nhost.auth.signInProviderURL(provider, options)
|
||||
Client->>Client: Redirect to returned URL
|
||||
|
||||
Client->>NhostAuth: GET /v1/signin/provider/{provider}
|
||||
Note over NhostAuth: Generate OAuth state & store session
|
||||
NhostAuth->>Provider: 302 Redirect to provider authorization
|
||||
|
||||
Provider->>Provider: User authorizes application
|
||||
Provider->>NhostAuth: 302 Callback with authorization code
|
||||
Note over NhostAuth: Exchange code for tokens<br/>Create/update user<br/>Generate refresh token
|
||||
|
||||
NhostAuth->>Client: 302 Redirect to redirectTo URL
|
||||
Note over Client: URL contains refreshToken<br/>or error information
|
||||
|
||||
Client->>Client: Extract refreshToken from URL
|
||||
Client->>NhostAuth: POST /v1/token with refreshToken
|
||||
NhostAuth->>Client: Return session with accessToken
|
||||
|
||||
Client->>Client: Store session & authenticate user
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Generate the Provider Sign-In URL
|
||||
|
||||
Use the `signInProviderURL()` method to generate the OAuth authorization URL. This method returns a URL that you'll redirect the user to:
|
||||
|
||||
```tsx
|
||||
import { nhost } from './lib/nhost';
|
||||
|
||||
const handleSocialSignIn = (provider: 'github' | 'google' | 'apple') => {
|
||||
// Get the current origin to build the callback URL
|
||||
const origin = window.location.origin;
|
||||
const redirectUrl = `${origin}/verify`;
|
||||
|
||||
// Generate the provider sign-in URL
|
||||
const url = nhost.auth.signInProviderURL(provider, {
|
||||
redirectTo: redirectUrl,
|
||||
});
|
||||
|
||||
// Redirect the user to the OAuth provider
|
||||
window.location.href = url;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. OAuth Provider Authorization
|
||||
|
||||
When the user is redirected to the OAuth provider (e.g., GitHub, Google), they will:
|
||||
|
||||
1. See a consent screen asking to authorize your application
|
||||
2. Grant or deny permission to access their profile information
|
||||
3. Be redirected back to Nhost Auth's callback URL
|
||||
|
||||
### 3. Nhost Auth Callback Processing
|
||||
|
||||
Nhost Auth receives the callback from the OAuth provider at `/v1/signin/provider/{provider}/callback` and performs the following:
|
||||
|
||||
1. **Validates the OAuth state** to prevent CSRF attacks
|
||||
2. **Exchanges the authorization code** for access and refresh tokens from the provider
|
||||
3. **Fetches the user's profile** from the provider
|
||||
4. **Creates or updates the user** in your Nhost database
|
||||
5. **Generates a Nhost refresh token** for the session
|
||||
6. **Redirects to your client application** at the `redirectTo` URL
|
||||
|
||||
### 4. Handle the Redirect
|
||||
|
||||
After successful authentication, Nhost redirects back to your `redirectTo` URL with query parameters. You need to handle two scenarios:
|
||||
|
||||
#### Success - Extract the Refresh Token
|
||||
|
||||
On success, the URL will contain a `refreshToken` parameter:
|
||||
|
||||
```
|
||||
https://your-app.com/verify?refreshToken=abc123...
|
||||
```
|
||||
|
||||
Extract this token and exchange it for a session:
|
||||
|
||||
```tsx
|
||||
import type { ErrorResponse } from '@nhost/nhost-js/auth';
|
||||
import type { FetchError } from '@nhost/nhost-js/fetch';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { nhost } from './lib/nhost';
|
||||
|
||||
export default function Verify() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying');
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const refreshToken = params.get('refreshToken');
|
||||
|
||||
if (!refreshToken) {
|
||||
setStatus('error');
|
||||
setError('No refresh token found in URL');
|
||||
return;
|
||||
}
|
||||
|
||||
let isMounted = true;
|
||||
|
||||
async function processToken() {
|
||||
try {
|
||||
// Exchange refresh token for session
|
||||
await nhost.auth.refreshToken({ refreshToken });
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
setStatus('success');
|
||||
|
||||
// Redirect to the application
|
||||
setTimeout(() => {
|
||||
if (isMounted) navigate('/profile');
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
const error = err as FetchError<ErrorResponse>;
|
||||
if (!isMounted) return;
|
||||
|
||||
setStatus('error');
|
||||
setError(`An error occurred during verification: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
processToken();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [location.search, navigate]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{status === 'verifying' && <p>Verifying...</p>}
|
||||
{status === 'success' && <p>Successfully verified! Redirecting...</p>}
|
||||
{status === 'error' && (
|
||||
<div>
|
||||
<p>Verification failed: {error}</p>
|
||||
<button onClick={() => navigate('/signin')}>Back to Sign In</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Error - Handle Authentication Failure
|
||||
|
||||
On error, the URL will contain error parameters:
|
||||
|
||||
```
|
||||
https://your-app.com/verify?error=access_denied
|
||||
```
|
||||
|
||||
You can handle these errors by checking for the `error` query parameter:
|
||||
|
||||
```tsx
|
||||
const params = new URLSearchParams(location.search);
|
||||
const error = params.get('error');
|
||||
|
||||
if (error) {
|
||||
// Handle error - redirect to sign-in page with error message
|
||||
navigate(`/signin?error=${encodeURIComponent(error)}`);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
Common error scenarios include:
|
||||
- User denied authorization at the OAuth provider
|
||||
- Invalid OAuth request configuration
|
||||
- Error from the OAuth provider
|
||||
- Provider account already linked to another user
|
||||
|
||||
### 5. Session Management
|
||||
|
||||
Once you've exchanged the refresh token for a session, the Nhost SDK automatically manages:
|
||||
|
||||
- **Access token** - Short-lived JWT for API requests (default: 15 minutes)
|
||||
- **Refresh token** - Used to obtain new access tokens (default: 30 days)
|
||||
- **Automatic token refresh** - The SDK refreshes tokens before expiration
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### CSRF Protection
|
||||
|
||||
Nhost automatically handles CSRF protection using the OAuth `state` parameter. Each sign-in request generates a unique state value that is validated during the callback.
|
||||
|
||||
### Redirect URL Validation
|
||||
|
||||
For security, Nhost validates that the `redirectTo` URL matches either your clientUrl or one of your configured allowed redirect URLs. Configure these in your Nhost project settings.
|
||||
|
||||
### Custom Domains
|
||||
|
||||
To use your own domain for the OAuth callback URL instead of the default Nhost domain, refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
|
||||
## Provider-Specific Setup
|
||||
|
||||
Each OAuth provider requires specific configuration. Refer to the provider-specific guides for detailed setup instructions:
|
||||
|
||||
- [Apple](/products/auth/providers/sign-in-apple)
|
||||
- [Azure AD / Entra ID](/products/auth/providers/sign-in-azuread)
|
||||
- [Bitbucket](/products/auth/providers/sign-in-bitbucket)
|
||||
- [Discord](/products/auth/providers/sign-in-discord)
|
||||
- [Facebook](/products/auth/providers/sign-in-facebook)
|
||||
- [GitHub](/products/auth/providers/sign-in-github)
|
||||
- [GitLab](/products/auth/providers/sign-in-gitlab)
|
||||
- [Google](/products/auth/providers/sign-in-google)
|
||||
- [LinkedIn](/products/auth/providers/sign-in-linkedin)
|
||||
- [Spotify](/products/auth/providers/sign-in-spotify)
|
||||
- [Strava](/products/auth/providers/sign-in-strava)
|
||||
- [Twitch](/products/auth/providers/sign-in-twitch)
|
||||
- [Windows Live](/products/auth/providers/sign-in-windowslive)
|
||||
- [WorkOS](/products/auth/providers/sign-in-workos)
|
||||
|
||||
## API Reference
|
||||
|
||||
For detailed API documentation, see:
|
||||
|
||||
- [signInProviderURL()](/reference/javascript/nhost-js/auth#signinproviderurl) in the JavaScript SDK reference
|
||||
- [GET /v1/signin/provider/{ '{provider}' }](/reference/auth/get-signin-provider-{provider}) in the API reference
|
||||
- [GET /v1/signin/provider/{ '{provider}' }/callback](/reference/auth/get-signin-provider-{provider}-callback) in the API reference
|
||||
@@ -42,14 +42,4 @@ icon: spotify
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'spotify'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Spotify as an OAuth provider in Nhost, you can sign in users using the Spotify provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -33,14 +33,4 @@ Due to Strava API updates, email is no longer returned and is intentionally left
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: "strava",
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Strava as an OAuth provider in Nhost, you can sign in users using the Strava provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -36,14 +36,4 @@ icon: twitch
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'twitch'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured Twitch as an OAuth provider in Nhost, you can sign in users using the Twitch provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -32,14 +32,4 @@ icon: windowslive
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: "windowslive",
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured WindowsLive as an OAuth provider in Nhost, you can sign in users using the WindowsLive provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -56,14 +56,4 @@ See the [WorkOS documentation](https://workos.com/docs/) to learn more about how
|
||||
|
||||
## Sign In Users
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'workos'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/cloud/custom-domains) documentation.
|
||||
</Note>
|
||||
Once you've configured WorkOS as an OAuth provider in Nhost, you can sign in users using the WorkOS provider. See the [OAuth Provider Sign-In Guide](/products/auth/providers/sign-in-provider) for detailed implementation instructions including the complete OAuth flow, error handling, and session management.
|
||||
|
||||
@@ -54,5 +54,10 @@
|
||||
"@types/react": "~19.0.14",
|
||||
"@types/node": "^22.15.17"
|
||||
},
|
||||
"private": true
|
||||
"private": true,
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
examples/demos/ReactNativeDemo/pnpm-lock.yaml
generated
43
examples/demos/ReactNativeDemo/pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1064,9 +1067,6 @@ packages:
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
@@ -1443,11 +1443,6 @@ packages:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1877,12 +1872,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsc-safe-url@0.2.4:
|
||||
@@ -2623,9 +2614,6 @@ packages:
|
||||
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3825,7 +3813,7 @@ snapshots:
|
||||
'@babel/code-frame': 7.10.4
|
||||
chalk: 4.1.2
|
||||
find-up: 5.0.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
@@ -3847,7 +3835,7 @@ snapshots:
|
||||
camelcase: 5.3.1
|
||||
find-up: 4.1.0
|
||||
get-package-type: 0.1.0
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
resolve-from: 5.0.0
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
@@ -4296,10 +4284,6 @@ snapshots:
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
asap@2.0.6: {}
|
||||
@@ -4615,7 +4599,7 @@ snapshots:
|
||||
dependencies:
|
||||
import-fresh: 2.0.0
|
||||
is-directory: 0.3.1
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 4.0.0
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -4698,8 +4682,6 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
@@ -5178,12 +5160,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -6011,8 +5988,6 @@ snapshots:
|
||||
|
||||
split-on-first@1.1.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
@@ -32,5 +32,10 @@
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
examples/guides/react-apollo/pnpm-lock.yaml
generated
13
examples/guides/react-apollo/pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1551,8 +1554,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
@@ -3103,7 +3106,7 @@ snapshots:
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
jose: 5.10.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
lodash: 4.17.21
|
||||
scuid: 1.1.0
|
||||
tslib: 2.8.1
|
||||
@@ -3613,7 +3616,7 @@ snapshots:
|
||||
cosmiconfig@8.3.6:
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
|
||||
@@ -3965,7 +3968,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
|
||||
@@ -33,5 +33,10 @@
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
examples/guides/react-query/pnpm-lock.yaml
generated
13
examples/guides/react-query/pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1534,8 +1537,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
@@ -3029,7 +3032,7 @@ snapshots:
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
jose: 5.10.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
lodash: 4.17.21
|
||||
scuid: 1.1.0
|
||||
tslib: 2.8.1
|
||||
@@ -3538,7 +3541,7 @@ snapshots:
|
||||
cosmiconfig@8.3.6:
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
|
||||
@@ -3886,7 +3889,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
|
||||
@@ -33,5 +33,10 @@
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
examples/guides/react-urql/pnpm-lock.yaml
generated
13
examples/guides/react-urql/pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1294,8 +1297,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
@@ -2500,7 +2503,7 @@ snapshots:
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
jose: 5.10.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
lodash: 4.17.21
|
||||
scuid: 1.1.0
|
||||
tslib: 2.8.1
|
||||
@@ -2940,7 +2943,7 @@ snapshots:
|
||||
cosmiconfig@8.3.6:
|
||||
dependencies:
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 5.2.0
|
||||
path-type: 4.0.0
|
||||
|
||||
@@ -3267,7 +3270,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
|
||||
@@ -3,8 +3,14 @@ table:
|
||||
schema: auth
|
||||
is_enum: true
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
column_config:
|
||||
comment:
|
||||
custom_name: comment
|
||||
value:
|
||||
custom_name: value
|
||||
custom_column_names:
|
||||
comment: comment
|
||||
value: value
|
||||
custom_name: authRefreshTokenTypes
|
||||
custom_root_fields:
|
||||
delete: deleteAuthRefreshTokenTypes
|
||||
|
||||
@@ -31,7 +31,7 @@ httpPoolSize = 100
|
||||
version = 22
|
||||
|
||||
[auth]
|
||||
version = '0.41.1'
|
||||
version = '0.43.1'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'disabled'
|
||||
@@ -183,7 +183,7 @@ capacity = 1
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.8.0-beta5'
|
||||
version = '0.9.1'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
|
||||
@@ -154,6 +154,11 @@ export default function Files() {
|
||||
const response = await nhost.storage.uploadFiles({
|
||||
"bucket-id": "personal",
|
||||
"file[]": [file as File],
|
||||
"metadata[]": [
|
||||
{
|
||||
metadata: { key1: "value1" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Get the processed file data
|
||||
|
||||
@@ -467,7 +467,12 @@ export default function Todos() {
|
||||
)}
|
||||
|
||||
{showAddForm && (
|
||||
<View style={[commonStyles.card, { marginHorizontal: 16, width: undefined }]}>
|
||||
<View
|
||||
style={[
|
||||
commonStyles.card,
|
||||
{ marginHorizontal: 16, width: undefined },
|
||||
]}
|
||||
>
|
||||
<Text style={commonStyles.cardTitle}>Add New Todo</Text>
|
||||
<View style={commonStyles.formFields}>
|
||||
<View style={commonStyles.fieldGroup}>
|
||||
|
||||
10386
examples/tutorials/nhost-reactnative-tutorial/package-lock.json
generated
10386
examples/tutorials/nhost-reactnative-tutorial/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,9 +17,11 @@
|
||||
"expo-crypto": "14",
|
||||
"expo-document-picker": "13",
|
||||
"expo-file-system": "18",
|
||||
"expo-linking": "^8.0.8",
|
||||
"expo-router": "~6",
|
||||
"expo-sharing": "13",
|
||||
"expo-status-bar": "~3.0.8",
|
||||
"metro-minify-terser": "^0.83.3",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.4"
|
||||
},
|
||||
@@ -27,5 +29,10 @@
|
||||
"@types/react": "~19.1.0",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"private": true
|
||||
"private": true,
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -32,6 +35,9 @@ importers:
|
||||
expo-file-system:
|
||||
specifier: '18'
|
||||
version: 18.1.11(expo@54.0.9)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))
|
||||
expo-linking:
|
||||
specifier: ^8.0.8
|
||||
version: 8.0.8(expo@54.0.9)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
|
||||
expo-router:
|
||||
specifier: ~6
|
||||
version: 6.0.7(@expo/metro-runtime@6.1.2)(@types/react@19.1.13)(expo-constants@18.0.9)(expo-linking@8.0.8)(expo@54.0.9)(react-dom@19.1.1(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
|
||||
@@ -41,6 +47,9 @@ importers:
|
||||
expo-status-bar:
|
||||
specifier: ~3.0.8
|
||||
version: 3.0.8(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)
|
||||
metro-minify-terser:
|
||||
specifier: ^0.83.3
|
||||
version: 0.83.3
|
||||
react:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0
|
||||
@@ -1221,9 +1230,6 @@ packages:
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
@@ -1617,11 +1623,6 @@ packages:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2021,12 +2022,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsc-safe-url@0.2.4:
|
||||
@@ -2232,6 +2229,10 @@ packages:
|
||||
resolution: {integrity: sha512-zvIxnh7U0JQ7vT4quasKsijId3dOAWgq+ip2jF/8TMrPUqQabGrs04L2dd0haQJ+PA+d4VvK/bPOY8X/vL2PWw==}
|
||||
engines: {node: '>=20.19.4'}
|
||||
|
||||
metro-minify-terser@0.83.3:
|
||||
resolution: {integrity: sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==}
|
||||
engines: {node: '>=20.19.4'}
|
||||
|
||||
metro-resolver@0.83.1:
|
||||
resolution: {integrity: sha512-t8j46kiILAqqFS5RNa+xpQyVjULxRxlvMidqUswPEk5nQVNdlJslqizDm/Et3v/JKwOtQGkYAQCHxP1zGStR/g==}
|
||||
engines: {node: '>=20.19.4'}
|
||||
@@ -2827,9 +2828,6 @@ packages:
|
||||
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4121,7 +4119,7 @@ snapshots:
|
||||
'@babel/code-frame': 7.10.4
|
||||
chalk: 4.1.2
|
||||
find-up: 5.0.0
|
||||
js-yaml: 4.1.0
|
||||
js-yaml: 4.1.1
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
@@ -4143,7 +4141,7 @@ snapshots:
|
||||
camelcase: 5.3.1
|
||||
find-up: 4.1.0
|
||||
get-package-type: 0.1.0
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
resolve-from: 5.0.0
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
@@ -4726,10 +4724,6 @@ snapshots:
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
@@ -5061,7 +5055,7 @@ snapshots:
|
||||
dependencies:
|
||||
import-fresh: 2.0.0
|
||||
is-directory: 0.3.1
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
parse-json: 4.0.0
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -5146,8 +5140,6 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
@@ -5597,12 +5589,7 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.1.0:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -5840,6 +5827,11 @@ snapshots:
|
||||
flow-enums-runtime: 0.0.6
|
||||
terser: 5.44.0
|
||||
|
||||
metro-minify-terser@0.83.3:
|
||||
dependencies:
|
||||
flow-enums-runtime: 0.0.6
|
||||
terser: 5.44.0
|
||||
|
||||
metro-resolver@0.83.1:
|
||||
dependencies:
|
||||
flow-enums-runtime: 0.0.6
|
||||
@@ -6588,8 +6580,6 @@ snapshots:
|
||||
|
||||
split-on-first@1.1.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
@@ -27,5 +27,10 @@
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^7.0.8"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"cookie@<0.7.0": ">=0.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
cookie@<0.7.0: '>=0.7.0'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -384,9 +387,9 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cookie@0.6.0:
|
||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
@@ -750,7 +753,7 @@ snapshots:
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.0(svelte@5.38.10)(vite@7.1.11)
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
cookie: 1.0.2
|
||||
devalue: 5.3.2
|
||||
esm-env: 1.2.2
|
||||
kleur: 4.1.5
|
||||
@@ -799,7 +802,7 @@ snapshots:
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
cookie@0.6.0: {}
|
||||
cookie@1.0.2: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
|
||||
@@ -125,7 +125,8 @@
|
||||
"form-data@<2.5.4": ">=2.5.4",
|
||||
"tmp@<=0.2.3": ">=0.2.4",
|
||||
"devalue@<5.3.2": ">=5.3.2",
|
||||
"axios@<1.12.0": ">=1.12.0"
|
||||
"axios@<1.12.0": ">=1.12.0",
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"rollup@<2.79.2": ">=2.79.2"
|
||||
"rollup@<2.79.2": ">=2.79.2",
|
||||
"js-yaml@<=4.1.0": ">=4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/nhost-js/pnpm-lock.yaml
generated
32
packages/nhost-js/pnpm-lock.yaml
generated
@@ -6,6 +6,7 @@ settings:
|
||||
|
||||
overrides:
|
||||
rollup@<2.79.2: '>=2.79.2'
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
@@ -531,8 +532,8 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
@@ -711,11 +712,6 @@ packages:
|
||||
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
@@ -1013,8 +1009,8 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
@@ -1250,9 +1246,6 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1614,7 +1607,7 @@ snapshots:
|
||||
camelcase: 5.3.1
|
||||
find-up: 4.1.0
|
||||
get-package-type: 0.1.0
|
||||
js-yaml: 3.14.1
|
||||
js-yaml: 4.1.1
|
||||
resolve-from: 5.0.0
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
@@ -1973,9 +1966,7 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
argparse@2.0.1: {}
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
@@ -2158,8 +2149,6 @@ snapshots:
|
||||
|
||||
escape-string-regexp@2.0.0: {}
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
execa@5.1.1:
|
||||
@@ -2635,10 +2624,9 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
js-yaml@4.1.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
argparse: 2.0.1
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
@@ -2849,8 +2837,6 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
@@ -632,16 +632,27 @@ export const createAPIClient = (
|
||||
): Promise<FetchResponse<UploadFilesResponse201>> => {
|
||||
const url = `${baseURL}/files`;
|
||||
const formData = new FormData();
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
(navigator as { product?: string }).product === "ReactNative";
|
||||
if (body["bucket-id"] !== undefined) {
|
||||
formData.append("bucket-id", body["bucket-id"]);
|
||||
}
|
||||
if (body["metadata[]"] !== undefined) {
|
||||
body["metadata[]"].forEach((value) => {
|
||||
formData.append(
|
||||
"metadata[]",
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
if (isReactNative) {
|
||||
formData.append("metadata[]", {
|
||||
string: JSON.stringify(value),
|
||||
type: "application/json",
|
||||
name: "",
|
||||
} as unknown as Blob);
|
||||
} else {
|
||||
formData.append(
|
||||
"metadata[]",
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (body["file[]"] !== undefined) {
|
||||
@@ -799,14 +810,25 @@ export const createAPIClient = (
|
||||
): Promise<FetchResponse<FileMetadata>> => {
|
||||
const url = `${baseURL}/files/${id}`;
|
||||
const formData = new FormData();
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
(navigator as { product?: string }).product === "ReactNative";
|
||||
if (body["metadata"] !== undefined) {
|
||||
formData.append(
|
||||
"metadata",
|
||||
new Blob([JSON.stringify(body["metadata"])], {
|
||||
if (isReactNative) {
|
||||
formData.append("metadata", {
|
||||
string: JSON.stringify(body["metadata"]),
|
||||
type: "application/json",
|
||||
}),
|
||||
"",
|
||||
);
|
||||
name: "",
|
||||
} as unknown as Blob);
|
||||
} else {
|
||||
formData.append(
|
||||
"metadata",
|
||||
new Blob([JSON.stringify(body["metadata"])], {
|
||||
type: "application/json",
|
||||
}),
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (body["file"] !== undefined) {
|
||||
formData.append("file", body["file"]);
|
||||
|
||||
1
pnpm-lock.yaml
generated
1
pnpm-lock.yaml
generated
@@ -90,6 +90,7 @@ overrides:
|
||||
tmp@<=0.2.3: '>=0.2.4'
|
||||
devalue@<5.3.2: '>=5.3.2'
|
||||
axios@<1.12.0: '>=1.12.0'
|
||||
js-yaml@<=4.1.0: '>=4.1.1'
|
||||
|
||||
importers:
|
||||
|
||||
|
||||
@@ -767,16 +767,30 @@ export const createAPIClient = (
|
||||
): Promise<FetchResponse<UploadFilesResponse201>> => {
|
||||
const url = `${ baseURL }/files/`;
|
||||
const formData = new FormData();
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
(navigator as { product?: string }).product === "ReactNative";
|
||||
if (body["bucket-id"] !== undefined) {
|
||||
formData.append("bucket-id", body["bucket-id"]);
|
||||
}
|
||||
if (body["metadata[]"] !== undefined) {
|
||||
body["metadata[]"].forEach((value) => {
|
||||
formData.append(
|
||||
if (isReactNative) {
|
||||
formData.append(
|
||||
"metadata[]",
|
||||
{
|
||||
string: JSON.stringify(value),
|
||||
type: "application/json",
|
||||
name: "",
|
||||
} as unknown as Blob,
|
||||
);
|
||||
} else {
|
||||
formData.append(
|
||||
"metadata[]",
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
)
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -912,12 +926,26 @@ export const createAPIClient = (
|
||||
): Promise<FetchResponse<FileMetadata>> => {
|
||||
const url = `${ baseURL }/files/${id}`;
|
||||
const formData = new FormData();
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
(navigator as { product?: string }).product === "ReactNative";
|
||||
if (body["metadata"] !== undefined) {
|
||||
formData.append(
|
||||
if (isReactNative) {
|
||||
formData.append(
|
||||
"metadata",
|
||||
{
|
||||
string: JSON.stringify(body["metadata"]),
|
||||
type: "application/json",
|
||||
name: "",
|
||||
} as unknown as Blob,
|
||||
);
|
||||
} else {
|
||||
formData.append(
|
||||
"metadata",
|
||||
new Blob([JSON.stringify(body["metadata"])], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
new Blob([JSON.stringify(body["metadata"])], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (body["file"] !== undefined) {
|
||||
formData.append("file", body["file"]);
|
||||
|
||||
@@ -126,6 +126,9 @@ export const createAPIClient = (
|
||||
});
|
||||
{{- else if .RequestFormData }}
|
||||
const formData = new FormData();
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
(navigator as { product?: string }).product === "ReactNative";
|
||||
|
||||
{{- range .RequestFormData.Properties }}
|
||||
{{- if eq .Type.Kind "scalar" }}
|
||||
@@ -138,11 +141,22 @@ export const createAPIClient = (
|
||||
{{- if eq .Type.Item.Kind "scalar" }}
|
||||
formData.append("{{ .Name }}", value)
|
||||
{{- else if eq .Type.Item.Kind "object" }}
|
||||
formData.append(
|
||||
if (isReactNative) {
|
||||
formData.append(
|
||||
"{{ .Name }}",
|
||||
{
|
||||
string: JSON.stringify(value),
|
||||
type: "application/json",
|
||||
name: "",
|
||||
} as unknown as Blob,
|
||||
);
|
||||
} else {
|
||||
formData.append(
|
||||
"{{ .Name }}",
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
)
|
||||
new Blob([JSON.stringify(value)], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
}
|
||||
{{- else }}
|
||||
TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }}
|
||||
{{- end }}
|
||||
@@ -151,11 +165,22 @@ export const createAPIClient = (
|
||||
}
|
||||
{{- else if eq .Type.Kind "object" }}
|
||||
if (body["{{ .Name }}"] !== undefined) {
|
||||
formData.append(
|
||||
if (isReactNative) {
|
||||
formData.append(
|
||||
"{{ .Name }}",
|
||||
{
|
||||
string: JSON.stringify(body["{{ .Name }}"]),
|
||||
type: "application/json",
|
||||
name: "",
|
||||
} as unknown as Blob,
|
||||
);
|
||||
} else {
|
||||
formData.append(
|
||||
"{{ .Name }}",
|
||||
new Blob([JSON.stringify(body["{{ .Name }}"])], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
new Blob([JSON.stringify(body["{{ .Name }}"])], { type: "application/json" }),
|
||||
"",
|
||||
);
|
||||
}
|
||||
}
|
||||
{{- else }}
|
||||
TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }}
|
||||
|
||||
Reference in New Issue
Block a user