Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot]
472559276c chore: update versions (#2079)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-storage-js@2.2.0

### Minor Changes

- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not
using `form-data`

## @nhost/apollo@5.2.13

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/react-apollo@5.0.30

### Patch Changes

-   @nhost/apollo@5.2.13
-   @nhost/react@2.0.26

## @nhost/react-urql@2.0.27

### Patch Changes

-   @nhost/react@2.0.26

## @nhost/nextjs@1.13.32

### Patch Changes

-   @nhost/react@2.0.26

## @nhost/nhost-js@2.2.11

### Patch Changes

-   Updated dependencies [2cdb13b3e]
    -   @nhost/hasura-storage-js@2.2.0

## @nhost/react@2.0.26

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/vue@1.13.31

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/dashboard@0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

## @nhost-examples/node-storage@0.0.3

### Patch Changes

- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not
using `form-data`
    -   @nhost/nhost-js@2.2.11

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-27 16:50:56 +02:00
Szilárd Dóró
2cdb13b3ef fix(hasura-storage-js): streamline file upload API (#2072)
Fixes #2071

This PR changes the API of the `hasura-storage-js` SDK slightly.

Before:
```ts
const formData = new FormData();

// first file
formData.append('file[]', '<file>');

// second file
formData.append('file[]', '<file>');

const { fileMetadata, error } = await nhost.storage.upload({
  formData,
  id: '<custom-uuid>', // ID doesn't make sense anymore when uploading multiple files
  name: '<custom-name>', // Name doesn't make sense anymore when uploading multiple files
});
```

Now:
```ts
const formData = new FormData();

// first file
formData.append('file[]', '<file>', '<custom-name>');
formData.append('metadata[]', JSON.stringify({ id: '<custom-uuid>' }))

// second file
formData.append('file[]', '<file>', '<custom-name>');
formData.append('metadata[]', JSON.stringify({ id: '<custom-uuid>' }))

const { fileMetadata, error } = await nhost.storage.upload({ formData });

// Access the metadata of upload files via fileMetadata.processedFiles
```

The `id` and `name` attributes can only be specified if you want to
upload a single file:

```ts
const file = event.target.files[0];

const { fileMetadata, error } = await nhost.storage.upload({
  file,
  id: '<custom-id>',
  name: '<custom-name>',
});

// Access the metadata of the upload file via fileMetadata
```
2023-06-27 16:20:17 +02:00
68 changed files with 1017 additions and 137 deletions

View File

@@ -1,5 +1,12 @@
# @nhost/dashboard
## 0.17.18
### Patch Changes
- @nhost/react-apollo@5.0.30
- @nhost/nextjs@1.13.32
## 0.17.17
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.17.17",
"version": "0.17.18",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -1 +1,3 @@
.secrets.nhost
.secrets
.nhost
cat.jpg

View File

@@ -1,5 +1,12 @@
# @nhost-examples/node-storage
## 0.0.3
### Patch Changes
- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not using `form-data`
- @nhost/nhost-js@2.2.11
## 0.0.2
### Patch Changes

View File

@@ -0,0 +1,6 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,14 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: HASURA_GRAPHQL_DATABASE_URL
isolation_level: read-committed
pool_settings:
connection_lifetime: 600
idle_timeout: 180
max_connections: 50
retries: 1
use_prepared_statements: true
tables: "!include default/tables/tables.yaml"

View File

@@ -0,0 +1,23 @@
table:
name: provider_requests
schema: auth
configuration:
column_config:
id:
custom_name: id
options:
custom_name: options
custom_column_names:
id: id
options: options
custom_name: authProviderRequests
custom_root_fields:
delete: deleteAuthProviderRequests
delete_by_pk: deleteAuthProviderRequest
insert: insertAuthProviderRequests
insert_one: insertAuthProviderRequest
select: authProviderRequests
select_aggregate: authProviderRequestsAggregate
select_by_pk: authProviderRequest
update: updateAuthProviderRequests
update_by_pk: updateAuthProviderRequest

View File

@@ -0,0 +1,28 @@
table:
name: providers
schema: auth
configuration:
column_config:
id:
custom_name: id
custom_column_names:
id: id
custom_name: authProviders
custom_root_fields:
delete: deleteAuthProviders
delete_by_pk: deleteAuthProvider
insert: insertAuthProviders
insert_one: insertAuthProvider
select: authProviders
select_aggregate: authProvidersAggregate
select_by_pk: authProvider
update: updateAuthProviders
update_by_pk: updateAuthProvider
array_relationships:
- name: userProviders
using:
foreign_key_constraint_on:
column: provider_id
table:
name: user_providers
schema: auth

View File

@@ -0,0 +1,26 @@
table:
name: refresh_token_types
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes
delete_by_pk: deleteAuthRefreshTokenType
insert: insertAuthRefreshTokenTypes
insert_one: insertAuthRefreshTokenType
select: authRefreshTokenTypes
select_aggregate: authRefreshTokenTypesAggregate
select_by_pk: authRefreshTokenType
update: updateAuthRefreshTokenTypes
update_by_pk: updateAuthRefreshTokenType
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: type
table:
name: refresh_tokens
schema: auth

View File

@@ -0,0 +1,55 @@
table:
name: refresh_tokens
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token_hash:
custom_name: refreshTokenHash
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt
refresh_token_hash: refreshTokenHash
user_id: userId
custom_name: authRefreshTokens
custom_root_fields:
delete: deleteAuthRefreshTokens
delete_by_pk: deleteAuthRefreshToken
insert: insertAuthRefreshTokens
insert_one: insertAuthRefreshToken
select: authRefreshTokens
select_aggregate: authRefreshTokensAggregate
select_by_pk: authRefreshToken
update: updateAuthRefreshTokens
update_by_pk: updateAuthRefreshToken
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
select_permissions:
- role: user
permission:
columns:
- id
- created_at
- expires_at
- metadata
- type
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
delete_permissions:
- role: user
permission:
filter:
_and:
- user_id:
_eq: X-Hasura-User-Id
- type:
_eq: pat

View File

@@ -0,0 +1,35 @@
table:
name: roles
schema: auth
configuration:
column_config:
role:
custom_name: role
custom_column_names:
role: role
custom_name: authRoles
custom_root_fields:
delete: deleteAuthRoles
delete_by_pk: deleteAuthRole
insert: insertAuthRoles
insert_one: insertAuthRole
select: authRoles
select_aggregate: authRolesAggregate
select_by_pk: authRole
update: updateAuthRoles
update_by_pk: updateAuthRole
array_relationships:
- name: userRoles
using:
foreign_key_constraint_on:
column: role
table:
name: user_roles
schema: auth
- name: usersByDefaultRole
using:
foreign_key_constraint_on:
column: default_role
table:
name: users
schema: auth

View File

@@ -0,0 +1,48 @@
table:
name: user_providers
schema: auth
configuration:
column_config:
access_token:
custom_name: accessToken
created_at:
custom_name: createdAt
id:
custom_name: id
provider_id:
custom_name: providerId
provider_user_id:
custom_name: providerUserId
refresh_token:
custom_name: refreshToken
updated_at:
custom_name: updatedAt
user_id:
custom_name: userId
custom_column_names:
access_token: accessToken
created_at: createdAt
id: id
provider_id: providerId
provider_user_id: providerUserId
refresh_token: refreshToken
updated_at: updatedAt
user_id: userId
custom_name: authUserProviders
custom_root_fields:
delete: deleteAuthUserProviders
delete_by_pk: deleteAuthUserProvider
insert: insertAuthUserProviders
insert_one: insertAuthUserProvider
select: authUserProviders
select_aggregate: authUserProvidersAggregate
select_by_pk: authUserProvider
update: updateAuthUserProviders
update_by_pk: updateAuthUserProvider
object_relationships:
- name: provider
using:
foreign_key_constraint_on: provider_id
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,36 @@
table:
name: user_roles
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
id:
custom_name: id
role:
custom_name: role
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
id: id
role: role
user_id: userId
custom_name: authUserRoles
custom_root_fields:
delete: deleteAuthUserRoles
delete_by_pk: deleteAuthUserRole
insert: insertAuthUserRoles
insert_one: insertAuthUserRole
select: authUserRoles
select_aggregate: authUserRolesAggregate
select_by_pk: authUserRole
update: updateAuthUserRoles
update_by_pk: updateAuthUserRole
object_relationships:
- name: roleByRole
using:
foreign_key_constraint_on: role
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,33 @@
table:
name: user_security_keys
schema: auth
configuration:
column_config:
credential_id:
custom_name: credentialId
credential_public_key:
custom_name: credentialPublicKey
id:
custom_name: id
user_id:
custom_name: userId
custom_column_names:
credential_id: credentialId
credential_public_key: credentialPublicKey
id: id
user_id: userId
custom_name: authUserSecurityKeys
custom_root_fields:
delete: deleteAuthUserSecurityKeys
delete_by_pk: deleteAuthUserSecurityKey
insert: insertAuthUserSecurityKeys
insert_one: insertAuthUserSecurityKey
select: authUserSecurityKeys
select_aggregate: authUserSecurityKeysAggregate
select_by_pk: authUserSecurityKey
update: updateAuthUserSecurityKeys
update_by_pk: updateAuthUserSecurityKey
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,122 @@
table:
name: users
schema: auth
configuration:
column_config:
active_mfa_type:
custom_name: activeMfaType
avatar_url:
custom_name: avatarUrl
created_at:
custom_name: createdAt
default_role:
custom_name: defaultRole
disabled:
custom_name: disabled
display_name:
custom_name: displayName
email:
custom_name: email
email_verified:
custom_name: emailVerified
id:
custom_name: id
is_anonymous:
custom_name: isAnonymous
last_seen:
custom_name: lastSeen
locale:
custom_name: locale
new_email:
custom_name: newEmail
otp_hash:
custom_name: otpHash
otp_hash_expires_at:
custom_name: otpHashExpiresAt
otp_method_last_used:
custom_name: otpMethodLastUsed
password_hash:
custom_name: passwordHash
phone_number:
custom_name: phoneNumber
phone_number_verified:
custom_name: phoneNumberVerified
ticket:
custom_name: ticket
ticket_expires_at:
custom_name: ticketExpiresAt
totp_secret:
custom_name: totpSecret
updated_at:
custom_name: updatedAt
webauthn_current_challenge:
custom_name: currentChallenge
custom_column_names:
active_mfa_type: activeMfaType
avatar_url: avatarUrl
created_at: createdAt
default_role: defaultRole
disabled: disabled
display_name: displayName
email: email
email_verified: emailVerified
id: id
is_anonymous: isAnonymous
last_seen: lastSeen
locale: locale
new_email: newEmail
otp_hash: otpHash
otp_hash_expires_at: otpHashExpiresAt
otp_method_last_used: otpMethodLastUsed
password_hash: passwordHash
phone_number: phoneNumber
phone_number_verified: phoneNumberVerified
ticket: ticket
ticket_expires_at: ticketExpiresAt
totp_secret: totpSecret
updated_at: updatedAt
webauthn_current_challenge: currentChallenge
custom_name: users
custom_root_fields:
delete: deleteUsers
delete_by_pk: deleteUser
insert: insertUsers
insert_one: insertUser
select: users
select_aggregate: usersAggregate
select_by_pk: user
update: updateUsers
update_by_pk: updateUser
object_relationships:
- name: defaultRoleByRole
using:
foreign_key_constraint_on: default_role
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: user_id
table:
name: refresh_tokens
schema: auth
- name: roles
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_roles
schema: auth
- name: securityKeys
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_security_keys
schema: auth
- name: userProviders
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_providers
schema: auth

View File

@@ -0,0 +1,49 @@
table:
name: buckets
schema: storage
configuration:
column_config:
cache_control:
custom_name: cacheControl
created_at:
custom_name: createdAt
download_expiration:
custom_name: downloadExpiration
id:
custom_name: id
max_upload_file_size:
custom_name: maxUploadFileSize
min_upload_file_size:
custom_name: minUploadFileSize
presigned_urls_enabled:
custom_name: presignedUrlsEnabled
updated_at:
custom_name: updatedAt
custom_column_names:
cache_control: cacheControl
created_at: createdAt
download_expiration: downloadExpiration
id: id
max_upload_file_size: maxUploadFileSize
min_upload_file_size: minUploadFileSize
presigned_urls_enabled: presignedUrlsEnabled
updated_at: updatedAt
custom_name: buckets
custom_root_fields:
delete: deleteBuckets
delete_by_pk: deleteBucket
insert: insertBuckets
insert_one: insertBucket
select: buckets
select_aggregate: bucketsAggregate
select_by_pk: bucket
update: updateBuckets
update_by_pk: updateBucket
array_relationships:
- name: files
using:
foreign_key_constraint_on:
column: bucket_id
table:
name: files
schema: storage

View File

@@ -0,0 +1,51 @@
table:
name: files
schema: storage
configuration:
column_config:
bucket_id:
custom_name: bucketId
created_at:
custom_name: createdAt
etag:
custom_name: etag
id:
custom_name: id
is_uploaded:
custom_name: isUploaded
mime_type:
custom_name: mimeType
name:
custom_name: name
size:
custom_name: size
updated_at:
custom_name: updatedAt
uploaded_by_user_id:
custom_name: uploadedByUserId
custom_column_names:
bucket_id: bucketId
created_at: createdAt
etag: etag
id: id
is_uploaded: isUploaded
mime_type: mimeType
name: name
size: size
updated_at: updatedAt
uploaded_by_user_id: uploadedByUserId
custom_name: files
custom_root_fields:
delete: deleteFiles
delete_by_pk: deleteFile
insert: insertFiles
insert_one: insertFile
select: files
select_aggregate: filesAggregate
select_by_pk: file
update: updateFiles
update_by_pk: updateFile
object_relationships:
- name: bucket
using:
foreign_key_constraint_on: bucket_id

View File

@@ -0,0 +1,11 @@
- "!include auth_provider_requests.yaml"
- "!include auth_providers.yaml"
- "!include auth_refresh_token_types.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_providers.yaml"
- "!include auth_user_roles.yaml"
- "!include auth_user_security_keys.yaml"
- "!include auth_users.yaml"
- "!include storage_buckets.yaml"
- "!include storage_files.yaml"

View File

@@ -0,0 +1 @@
disabled_for_roles: []

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
version: 3

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/node-storage",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"description": "This is an example of how to use the Storage with Node.js",
"main": "src/index.mjs",
@@ -14,9 +14,12 @@
"@nhost/nhost-js": "*",
"dotenv": "^16.1.3",
"form-data": "^4.0.0",
"node-fetch": "^3.3.0"
"fs-extra": "^11.1.1",
"node-fetch": "^3.3.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/node": "^18.15.11"
"@types/node": "^18.15.11",
"@types/uuid": "^9.0.1"
}
}

View File

@@ -10,14 +10,23 @@ dependencies:
form-data:
specifier: ^4.0.0
version: 4.0.0
fs-extra:
specifier: ^11.1.1
version: 11.1.1
node-fetch:
specifier: ^3.3.0
version: 3.3.0
uuid:
specifier: ^9.0.0
version: 9.0.0
devDependencies:
'@types/node':
specifier: ^18.15.11
version: 18.15.11
'@types/uuid':
specifier: ^9.0.1
version: 9.0.1
packages:
@@ -32,6 +41,10 @@ packages:
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
dev: true
/@types/uuid@9.0.1:
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
@@ -92,6 +105,27 @@ packages:
fetch-blob: 3.2.0
dev: false
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: false
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.11
dev: false
/jwt-decode@3.1.2:
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
dev: false
@@ -142,6 +176,16 @@ packages:
engines: {node: '>=4'}
dev: false
/universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
dev: false
/uuid@9.0.0:
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
hasBin: true
dev: false
/web-streams-polyfill@3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}

View File

@@ -1,4 +1,7 @@
import { NhostClient } from '@nhost/nhost-js'
import dotenv from 'dotenv'
dotenv.config()
/**
* Create a new Nhost client.

View File

@@ -1,62 +1,12 @@
import dotenv from 'dotenv'
import FormData from 'form-data'
import fetch from 'node-fetch'
import { createClient } from './client.mjs'
import { uploadFile } from './uploadFile.mjs'
import { uploadFormData } from './uploadFormData.mjs'
dotenv.config()
async function uploadFiles() {
await uploadFormData()
const client = createClient()
console.info('-----')
async function uploadImage() {
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error('Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const formData = new FormData()
formData.append('file[]', Buffer.from(arrayBuffer), 'cat.jpg')
// Upload file to Nhost Storage
const { error: uploadError, fileMetadata } = await client.storage.upload({
formData,
headers: { ...formData.getHeaders() }
})
if (uploadError) {
console.error(uploadError)
return
}
console.info(`File has been uploaded successfully!`)
const uploadedFile =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
console.info(`ID: ${uploadedFile?.id}`)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: blurredImage } =
await client.storage.getPresignedUrl({ fileId: uploadedFile.id })
if (presignError) {
console.error(presignError)
return
}
console.info(`Presigned URL: ${blurredImage.url}`)
} catch (error) {
console.error(error.message)
}
await uploadFile()
}
uploadImage()
uploadFiles()

View File

@@ -0,0 +1,70 @@
import fs from 'fs'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
import { createClient } from './client.mjs'
const client = createClient()
export async function uploadFile() {
console.info('Uploading a Single File Directly...')
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error(`[file]`, 'Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const fileBuffer = Buffer.from(arrayBuffer)
const fileName = 'cat.jpg'
fs.writeFile(fileName, fileBuffer, async (err) => {
if (err) {
console.error(`[file]`, err)
return
}
const file = fs.createReadStream(fileName)
const customFileId = uuidv4()
const { error: uploadError, fileMetadata } = await client.storage.upload({
file,
id: customFileId
})
if (uploadError) {
console.error(`[file]`, uploadError)
return
}
console.info(`[file]`, `File has been uploaded successfully!`)
console.info(`[file]`, `ID: ${fileMetadata?.id}`)
console.info(`[file]`, `Matches custom ID: ${fileMetadata?.id === customFileId}`)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: image } = await client.storage.getPresignedUrl({
fileId: fileMetadata.id
})
if (presignError) {
console.error(`[file]`, presignError)
return
}
console.info(`[file]`, `Presigned URL: ${image.url}`)
})
// Upload file to Nhost Storage
} catch (error) {
console.error(`[file]`, error.message)
}
}

View File

@@ -0,0 +1,90 @@
import FormData from 'form-data'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
import { createClient } from './client.mjs'
const client = createClient()
export async function uploadFormData() {
console.info('Uploading 2 Files via Form Data...')
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error(`[form-data]`, 'Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const customValues = [
{
id: uuidv4(),
name: 'cat1.jpg'
},
{
id: uuidv4(),
name: 'cat2.jpg'
}
]
const formData = new FormData()
formData.append('file[]', Buffer.from(arrayBuffer), customValues[0].name)
formData.append('metadata[]', JSON.stringify({ id: customValues[0].id }))
formData.append('file[]', Buffer.from(arrayBuffer), customValues[1].name)
formData.append('metadata[]', JSON.stringify({ id: customValues[1].id }))
// Upload files to Nhost Storage
const { error: uploadError, fileMetadata } = await client.storage.upload({
formData,
headers: { ...formData.getHeaders() }
})
if (uploadError) {
console.error(`[form-data]`, uploadError)
return
}
if (fileMetadata.processedFiles.length !== 2) {
console.error(
`[form-data]`,
`Expected 2 files to be uploaded, but got ${fileMetadata.processedFiles.length}`
)
return
}
await Promise.all(
fileMetadata?.processedFiles.map(async (processedFile, index) => {
console.info(`[form-data]`, `File has been uploaded successfully!`)
console.info(`[form-data]`, `ID: ${processedFile.id}`)
console.info(
`[form-data]`,
`Matches custom ID: ${customValues.some(({ id }) => processedFile.id === id)}`
)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: image } = await client.storage.getPresignedUrl({
fileId: processedFile.id
})
if (presignError) {
console.error(`[form-data]`, presignError)
return
}
console.info(`[form-data]`, `Presigned URL: ${image.url}`)
})
)
} catch (error) {
console.error(`[form-data]`, error.message)
}
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"allowJs": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"strictNullChecks": false
}
}

View File

@@ -1,5 +1,11 @@
# @nhost/apollo
## 5.2.13
### Patch Changes
- @nhost/nhost-js@2.2.11
## 5.2.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "5.2.12",
"version": "5.2.13",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,12 @@
# @nhost/react-apollo
## 5.0.30
### Patch Changes
- @nhost/apollo@5.2.13
- @nhost/react@2.0.26
## 5.0.29
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "5.0.29",
"version": "5.0.30",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/react-urql
## 2.0.27
### Patch Changes
- @nhost/react@2.0.26
## 2.0.26
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "2.0.26",
"version": "2.0.27",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/hasura-storage-js
## 2.2.0
### Minor Changes
- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not using `form-data`
## 2.1.6
### Patch Changes

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,26 @@
table:
name: refresh_token_types
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes
delete_by_pk: deleteAuthRefreshTokenType
insert: insertAuthRefreshTokenTypes
insert_one: insertAuthRefreshTokenType
select: authRefreshTokenTypes
select_aggregate: authRefreshTokenTypesAggregate
select_by_pk: authRefreshTokenType
update: updateAuthRefreshTokenTypes
update_by_pk: updateAuthRefreshTokenType
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: type
table:
name: refresh_tokens
schema: auth

View File

@@ -7,14 +7,14 @@ configuration:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token:
custom_name: refreshToken
refresh_token_hash:
custom_name: refreshTokenHash
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt
refresh_token: refreshToken
refresh_token_hash: refreshTokenHash
user_id: userId
custom_name: authRefreshTokens
custom_root_fields:
@@ -31,3 +31,25 @@ object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
select_permissions:
- role: user
permission:
columns:
- id
- created_at
- expires_at
- metadata
- type
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
delete_permissions:
- role: user
permission:
filter:
_and:
- user_id:
_eq: X-Hasura-User-Id
- type:
_eq: pat

View File

@@ -1,5 +1,6 @@
- "!include auth_provider_requests.yaml"
- "!include auth_providers.yaml"
- "!include auth_refresh_token_types.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_providers.yaml"

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/hasura-storage-js",
"version": "2.1.6",
"version": "2.2.0",
"description": "Hasura-storage client",
"license": "MIT",
"keywords": [

View File

@@ -1,11 +1,14 @@
import fetchPonyfill from 'fetch-ponyfill'
import FormData from 'form-data'
import {
ApiDeleteParams,
ApiDeleteResponse,
ApiGetPresignedUrlParams,
ApiGetPresignedUrlResponse,
StorageUploadFileParams,
StorageUploadFileResponse,
StorageUploadFormDataParams,
StorageUploadResponse
StorageUploadFormDataResponse
} from './utils/types'
import { fetchUpload } from './utils/upload'
@@ -24,21 +27,65 @@ export class HasuraStorageApi {
this.url = url
}
async upload({
async uploadFormData({
formData,
headers,
bucketId
}: StorageUploadFormDataParams): Promise<StorageUploadFormDataResponse> {
const { error, fileMetadata } = await fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId,
headers
})
if (error) {
return { fileMetadata: null, error }
}
if (fileMetadata && !('processedFiles' in fileMetadata)) {
return {
fileMetadata: {
processedFiles: [fileMetadata]
},
error: null
}
}
return { fileMetadata, error: null }
}
async uploadFile({
file,
bucketId,
id,
name
}: StorageUploadFormDataParams): Promise<StorageUploadResponse> {
return fetchUpload(this.url, formData, {
}: StorageUploadFileParams): Promise<StorageUploadFileResponse> {
const formData = new FormData()
formData.append('file[]', file)
formData.append('metadata[]', JSON.stringify({ id, name }))
const { error, fileMetadata } = await fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId,
fileId: id,
name,
headers
name
})
if (error) {
return { fileMetadata: null, error }
}
if (fileMetadata && 'processedFiles' in fileMetadata) {
return {
fileMetadata: fileMetadata.processedFiles[0],
error: null
}
}
return { fileMetadata, error: null }
}
async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> {

View File

@@ -1,4 +1,3 @@
import FormData from 'form-data'
import { HasuraStorageApi } from './hasura-storage-api'
import {
appendImageTransformationParameters,
@@ -8,7 +7,9 @@ import {
StorageGetPresignedUrlResponse,
StorageGetUrlParams,
StorageUploadFileParams,
StorageUploadFileResponse,
StorageUploadFormDataParams,
StorageUploadFormDataResponse,
StorageUploadParams,
StorageUploadResponse
} from './utils'
@@ -73,21 +74,14 @@ export class HasuraStorageClient {
* @docs https://docs.nhost.io/reference/javascript/storage/upload
*/
async upload(params: StorageUploadFileParams): Promise<StorageUploadResponse>
async upload(params: StorageUploadFormDataParams): Promise<StorageUploadResponse>
async upload(params: StorageUploadFileParams): Promise<StorageUploadFileResponse>
async upload(params: StorageUploadFormDataParams): Promise<StorageUploadFormDataResponse>
async upload(params: StorageUploadParams): Promise<StorageUploadResponse> {
if ('file' in params) {
const formData = new FormData()
formData.append('file[]', params.file)
return this.api.upload({
...params,
formData
})
return this.api.uploadFile(params)
}
return this.api.upload(params)
return this.api.uploadFormData(params)
}
/**

View File

@@ -36,8 +36,6 @@ export interface StorageUploadFileParams {
// works in browser and server
export interface StorageUploadFormDataParams {
formData: FormData
id?: string
name?: string
bucketId?: string
headers?: Record<string, string>
}
@@ -45,10 +43,16 @@ export interface StorageUploadFormDataParams {
// works in browser and server
export type StorageUploadParams = StorageUploadFileParams | StorageUploadFormDataParams
export type StorageUploadResponse =
| { fileMetadata: FileResponse | { processedFiles: FileResponse[] }; error: null }
export type StorageUploadFileResponse =
| { fileMetadata: FileResponse; error: null }
| { fileMetadata: null; error: StorageErrorPayload }
export type StorageUploadFormDataResponse =
| { fileMetadata: { processedFiles: FileResponse[] }; error: null }
| { fileMetadata: null; error: StorageErrorPayload }
export type StorageUploadResponse = StorageUploadFileResponse | StorageUploadFormDataResponse
export interface StorageImageTransformationParams {
/** Image width, in pixels */
width?: number

View File

@@ -38,15 +38,8 @@ export const fetchUpload = async (
const headers: HeadersInit = {
...initialHeaders
}
if (fileId) {
headers['x-nhost-file-id'] = fileId
}
if (bucketId) {
headers['x-nhost-bucket-id'] = bucketId
}
if (name) {
headers['x-nhost-file-name'] = toIso88591(name)
data.append('bucketId', bucketId)
}
if (adminSecret) {
headers['x-hasura-admin-secret'] = adminSecret

View File

@@ -1,17 +1,15 @@
import fs from 'fs'
import { describe, expect, it } from 'vitest'
import { v4 as uuidv4 } from 'uuid'
import { describe, expect, it } from 'vitest'
import { storage } from './utils/helpers'
import FormData from 'form-data'
describe('test delete file', () => {
it('should be able to get delete file', async () => {
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata } = await storage.upload({
formData: fd
file: file as unknown as File
})
const { error } = await storage.delete({

View File

@@ -38,11 +38,10 @@ describe('test upload', () => {
it('should upload a file with specific id', async () => {
const RANDOM_UUID = uuidv4()
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
id: RANDOM_UUID
})
@@ -50,8 +49,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { id: fileId } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { id: fileId } = fileMetadata
expect(error).toBeNull()
expect(fileId).toBe(RANDOM_UUID)
@@ -60,11 +58,10 @@ describe('test upload', () => {
it('should upload a file with specific name', async () => {
const FILE_NAME = 'special-name.pdf'
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
name: FILE_NAME
})
@@ -72,19 +69,17 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileName).toBe(FILE_NAME)
})
it('should upload a file with a non-ISO 8859-1 name', async () => {
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
name: '你 好'
})
@@ -92,8 +87,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileName).toMatchInlineSnapshot('"%E4%BD%A0%20%E5%A5%BD"')
@@ -103,11 +97,10 @@ describe('test upload', () => {
const RANDOM_UUID = uuidv4()
const FILE_NAME = 'special-name.pdf'
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
id: RANDOM_UUID,
name: FILE_NAME
})
@@ -116,8 +109,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { id: fileId, name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { id: fileId, name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileId).toBe(RANDOM_UUID)

View File

@@ -1,5 +1,11 @@
# @nhost/nextjs
## 1.13.32
### Patch Changes
- @nhost/react@2.0.26
## 1.13.31
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nextjs",
"version": "1.13.31",
"version": "1.13.32",
"description": "Nhost NextJS library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,12 @@
# @nhost/nhost-js
## 2.2.11
### Patch Changes
- Updated dependencies [2cdb13b3e]
- @nhost/hasura-storage-js@2.2.0
## 2.2.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nhost-js",
"version": "2.2.10",
"version": "2.2.11",
"description": "Nhost JavaScript SDK",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/react
## 2.0.26
### Patch Changes
- @nhost/nhost-js@2.2.11
## 2.0.25
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react",
"version": "2.0.25",
"version": "2.0.26",
"description": "Nhost React library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/vue
## 1.13.31
### Patch Changes
- @nhost/nhost-js@2.2.11
## 1.13.30
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/vue",
"version": "1.13.30",
"version": "1.13.31",
"description": "Nhost Vue library",
"license": "MIT",
"keywords": [

31
pnpm-lock.yaml generated
View File

@@ -861,13 +861,22 @@ importers:
form-data:
specifier: ^4.0.0
version: 4.0.0
fs-extra:
specifier: ^11.1.1
version: 11.1.1
node-fetch:
specifier: ^3.3.0
version: 3.3.0
uuid:
specifier: ^9.0.0
version: 9.0.0
devDependencies:
'@types/node':
specifier: ^18.15.11
version: 18.16.14
'@types/uuid':
specifier: ^9.0.1
version: 9.0.1
examples/react-apollo:
dependencies:
@@ -13493,7 +13502,6 @@ packages:
/@types/node@18.11.17:
resolution: {integrity: sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==}
dev: true
/@types/node@18.11.9:
resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==}
@@ -19270,7 +19278,7 @@ packages:
resolution: {integrity: sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==}
engines: {node: '>=10.13.0'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
tapable: 2.2.1
/enhanced-resolve@5.14.0:
@@ -21564,11 +21572,20 @@ packages:
jsonfile: 6.1.0
universalify: 2.0.0
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
/fs-extra@7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
engines: {node: '>=6 <7 || >=8'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
jsonfile: 4.0.0
universalify: 0.1.2
dev: true
@@ -22035,10 +22052,6 @@ packages:
'@gqty/utils': 1.0.0
graphql: 16.7.1
/graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
requiresBuild: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
requiresBuild: true
@@ -26893,7 +26906,7 @@ packages:
dependencies:
lilconfig: 2.0.6
postcss: 8.4.20
ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.4)
ts-node: 10.9.1(@types/node@18.11.17)(typescript@4.9.4)
yaml: 1.10.2
/postcss-loader@4.3.0(postcss@7.0.39)(webpack@4.46.0):
@@ -30941,6 +30954,7 @@ packages:
typescript: 4.9.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
@@ -31002,7 +31016,6 @@ packages:
typescript: 4.9.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-node@10.9.1(@types/node@18.11.9)(typescript@4.7.4):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}