From 2ca3d66b0b2c8048fddbd36e11ac56ea92de3d8f Mon Sep 17 00:00:00 2001 From: David Barroso Date: Fri, 8 Aug 2025 15:56:37 +0200 Subject: [PATCH 1/7] asd --- docs/reference/javascript/nhost-js/auth.mdx | 93 ++-- packages/nhost-js/api/auth.yaml | 449 ++++++++++-------- packages/nhost-js/src/auth/client.ts | 100 ++-- pnpm-lock.yaml | 59 +-- pnpm-workspace.yaml | 4 + tools/codegen/processor/intermediate_test.go | 3 + tools/codegen/processor/methods.go | 30 +- tools/codegen/processor/testdata/content.yaml | 144 ++++++ .../processor/testdata/content.yaml.ts | 149 ++++++ .../processor/typescript/templates/types.tmpl | 2 +- 10 files changed, 672 insertions(+), 361 deletions(-) create mode 100644 tools/codegen/processor/testdata/content.yaml create mode 100644 tools/codegen/processor/testdata/content.yaml.ts diff --git a/docs/reference/javascript/nhost-js/auth.mdx b/docs/reference/javascript/nhost-js/auth.mdx index e9728705..eb24bb9f 100644 --- a/docs/reference/javascript/nhost-js/auth.mdx +++ b/docs/reference/javascript/nhost-js/auth.mdx @@ -287,6 +287,7 @@ addSecurityKey(options?: RequestInit): Promise>; ``` -Summary: Change user password. The user must be authenticated or provide a ticket +Summary: Change user password +Change the user's password. The user must be authenticated with elevated permissions or provide a valid password reset ticket. This method may return different T based on the response code: @@ -378,6 +381,7 @@ createPAT(body: CreatePATRequest, options?: RequestInit): Promise>; ``` -Summary: Deanonymize an anonymous user in adding missing email or email+password, depending on the chosen authentication method. Will send a confirmation email if the server is configured to do so +Summary: Deanonymize an anonymous user +Convert an anonymous user to a regular user by adding email and optionally password credentials. A confirmation email will be sent if the server is configured to do so. This method may return different T based on the response code: @@ -447,6 +452,7 @@ getJWKs(options?: RequestInit): Promise>; ``` Summary: Get public keys for JWT verification in JWK Set format +Retrieve the JSON Web Key Set (JWKS) containing public keys used to verify JWT signatures. This endpoint is used by clients to validate access tokens. This method may return different T based on the response code: @@ -469,6 +475,7 @@ getUser(options?: RequestInit): Promise>; ``` Summary: Get user information +Retrieve the authenticated user's profile information including roles, metadata, and account status. This method may return different T based on the response code: @@ -560,6 +567,7 @@ linkIdToken(body: LinkIdTokenRequest, options?: RequestInit): Promise>; ``` -Summary: Request a password reset. An email with a verification link will be sent to the user's address +Summary: Request password reset +Request a password reset for a user account. An email with a verification link will be sent to the user's email address to complete the password reset process. This method may return different T based on the response code: @@ -646,6 +655,7 @@ sendVerificationEmail(body: UserEmailSendVerificationEmailRequest, options?: Req ``` Summary: Send verification email +Send an email verification link to the specified email address. Used to verify email addresses for new accounts or email changes. This method may return different T based on the response code: @@ -669,6 +679,7 @@ signInAnonymous(body?: SignInAnonymousRequest, options?: RequestInit): Promise>; ``` -Summary: Sign in with in an id token +Summary: Sign in with an ID token +Authenticate using an ID token from a supported OAuth provider (Apple or Google). Creates a new user account if one doesn't exist. This method may return different T based on the response code: @@ -738,7 +750,8 @@ This method may return different T based on the response code: signInOTPEmail(body: SignInOTPEmailRequest, options?: RequestInit): Promise>; ``` -Summary: Sign in with a one time password sent to user's email. If the user doesn't exist, it will be created. The options object is optional and can be used to configure the user's when signing up a new user. It is ignored if the user already exists. +Summary: Sign in with email OTP +Initiate email-based one-time password authentication. Sends an OTP to the specified email address. If the user doesn't exist, a new account will be created with the provided options. This method may return different T based on the response code: @@ -785,7 +798,8 @@ This method may return different T based on the response code: signInPasswordlessSms(body: SignInPasswordlessSmsRequest, options?: RequestInit): Promise>; ``` -Summary: Sign in with a one time password sent to user's phone number. If the user doesn't exist, it will be created. The options object is optional and can be used to configure the user's when signing up a new user. It is ignored if the user already exists. +Summary: Sign in with SMS OTP +Initiate passwordless authentication by sending a one-time password to the user's phone number. If the user doesn't exist, a new account will be created with the provided options. This method may return different T based on the response code: @@ -809,6 +823,7 @@ signInPAT(body: SignInPATRequest, options?: RequestInit): Promise>; ``` -Summary: Verify OTP and return a session if validation is successful +Summary: Verify email OTP +Complete email OTP authentication by verifying the one-time password. Returns a session if validation is successful. This method may return different T based on the response code: @@ -1071,7 +1088,8 @@ This method may return different T based on the response code: verifySignInPasswordlessSms(body: SignInPasswordlessSmsOtpRequest, options?: RequestInit): Promise>; ``` -Summary: Verify SMS OTP and return a session if validation is successful +Summary: Verify SMS OTP +Complete passwordless SMS authentication by verifying the one-time password. Returns a session if validation is successful. This method may return different T based on the response code: @@ -1142,7 +1160,8 @@ This method may return different T based on the response code: verifyTicketURL(params?: VerifyTicketParams, options?: RequestInit): string; ``` -Summary: Verify tickets created by email verification, email passwordless authentication (magic link), or password reset +Summary: Verify email and authentication tickets +Verify tickets created by email verification, magic link authentication, or password reset processes. Redirects the user to the appropriate destination upon successful verification. As this method is a redirect, it returns a URL string instead of a Promise @@ -1164,7 +1183,7 @@ verifyToken(body?: VerifyTokenRequest, options?: RequestInit): Promise; +optional metadata: string; ``` -Additional metadata for the user +Additional metadata for the user (JSON encoded string) ##### redirectTo? @@ -2663,50 +2682,6 @@ roles: string[]; --- -### UserAddSecurityKeyVerifyRequest - -#### Properties - -##### credential - -```ts -credential: Record; -``` - -(`Record`) - - -##### nickname? - -```ts -optional nickname: string; -``` - -Optional nickname for the security key - ---- - -### UserAddSecurityKeyVerifyResponse - -#### Properties - -##### id - -```ts -id: string; -``` - -(`string`) - ID of the newly added security key - -##### nickname? - -```ts -optional nickname: string; -``` - -Nickname of the security key - ---- - ### UserDeanonymizeRequest #### Properties diff --git a/packages/nhost-js/api/auth.yaml b/packages/nhost-js/api/auth.yaml index ff630d61..f99e7410 100644 --- a/packages/nhost-js/api/auth.yaml +++ b/packages/nhost-js/api/auth.yaml @@ -1,4 +1,5 @@ openapi: "3.0.0" + info: version: 1.0.0 title: "Nhost Authentication API" @@ -11,13 +12,34 @@ info: email: "support@nhost.io" url: "https://nhost.io" +servers: + - url: "https://{subdomain}.auth.{region}.nhost.run/v1" + description: "Nhost Authentication API Server" + +tags: + - name: authentication + description: "User authentication operations including sign-in, sign-up, and various authentication methods (email/password, passwordless, OAuth, WebAuthn, MFA)" + - name: security + description: "Security-related operations including Personal Access Tokens, WebAuthn management, account elevation, and account linking" + - name: session + description: "Session management operations including token refresh, verification, and sign-out" + - name: user + description: "User profile and account management operations including email/password changes, MFA configuration, and profile updates" + - name: system + description: "System operations including health checks, service version, and public key endpoints" + - name: verification + description: "Email and ticket verification operations for confirming user actions" + - name: excludeme + description: "These operations are not intended to be used directly by clients and should be excluded from client SDKs" + paths: /.well-known/jwks.json: get: summary: Get public keys for JWT verification in JWK Set format + description: Retrieve the JSON Web Key Set (JWKS) containing public keys used to verify JWT signatures. This endpoint is used by clients to validate access tokens. operationId: getJWKs tags: - - keys + - system responses: "200": content: @@ -26,6 +48,12 @@ paths: $ref: "#/components/schemas/JWKSet" description: >- The public keys in JWK Set format + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /elevate/webauthn: post: @@ -33,8 +61,7 @@ paths: description: Generate a Webauthn challenge for elevating user permissions operationId: elevateWebauthn tags: - - elevate - - webauthn + - security security: - BearerAuth: [] responses: @@ -58,12 +85,11 @@ paths: description: Complete Webauthn elevation by verifying the authentication response operationId: verifyElevateWebauthn tags: - - elevate - - webauthn - - verify + - security security: - BearerAuth: [] requestBody: + description: WebAuthn credential assertion response for elevation verification content: application/json: schema: @@ -86,8 +112,8 @@ paths: /healthz: get: - summary: "Health check (GET)" - description: "Verify if the authentication service is operational using GET method" + summary: Health check (GET) + description: Verify if the authentication service is operational using GET method operationId: healthCheckGet tags: - system @@ -98,27 +124,40 @@ paths: application/json: schema: $ref: "#/components/schemas/OKResponse" + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" head: - summary: "Health check (HEAD)" - description: "Verify if the authentication service is operational using HEAD method" + summary: Health check (HEAD) + description: Verify if the authentication service is operational using HEAD method operationId: healthCheckHead tags: - system responses: "200": description: "Service is healthy and operational" + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /link/idtoken: post: summary: Link a user account with the provider's account using an id token + description: Link the authenticated user's account with an external OAuth provider account using an ID token. Requires elevated permissions. operationId: linkIdToken tags: - - link - - idtoken + - security security: - BearerAuthElevated: [] requestBody: + description: ID token and provider information for account linking content: application/json: schema: @@ -141,12 +180,11 @@ paths: /mfa/totp/generate: get: - summary: "Generate TOTP secret" - description: "Generate a Time-based One-Time Password (TOTP) secret for setting up multi-factor authentication" + summary: Generate TOTP secret + description: Generate a Time-based One-Time Password (TOTP) secret for setting up multi-factor authentication operationId: changeUserMfa tags: - - mfa - - user-management + - user security: - BearerAuth: [] responses: @@ -166,12 +204,14 @@ paths: /pat: post: summary: Create a Personal Access Token (PAT) + description: Generate a new Personal Access Token for programmatic API access. PATs are long-lived tokens that can be used instead of regular authentication for automated systems. Requires elevated permissions. operationId: createPAT tags: - - pat + - security security: - BearerAuthElevated: [] requestBody: + description: Personal Access Token creation request with expiration and metadata content: application/json: schema: @@ -195,11 +235,12 @@ paths: /signin/anonymous: post: summary: Sign in anonymously + description: Create an anonymous user session without providing credentials. Anonymous users can be converted to regular users later via the deanonymize endpoint. operationId: signInAnonymous tags: - - signin - - anonymous + - authentication requestBody: + description: Optional user profile information for anonymous sign-in content: application/json: schema: @@ -222,13 +263,13 @@ paths: /signin/email-password: post: - summary: "Sign in with email and password" - description: "Authenticate a user with their email and password. Returns a session object or MFA challenge if two-factor authentication is enabled." + summary: Sign in with email and password + description: Authenticate a user with their email and password. Returns a session object or MFA challenge if two-factor authentication is enabled. operationId: signInEmailPassword tags: - authentication - - email-and-password requestBody: + description: User credentials for email and password authentication content: application/json: schema: @@ -250,14 +291,13 @@ paths: /signin/idtoken: post: - summary: Sign in with in an id token + summary: Sign in with an ID token + description: Authenticate using an ID token from a supported OAuth provider (Apple or Google). Creates a new user account if one doesn't exist. operationId: signInIdToken tags: - - signin - - idtoken - - apple - - google + - authentication requestBody: + description: ID token and provider information for authentication content: application/json: schema: @@ -280,13 +320,13 @@ paths: /signin/mfa/totp: post: - summary: "Verify TOTP for MFA" - description: "Complete the multi-factor authentication by verifying a Time-based One-Time Password (TOTP). Returns a session if validation is successful." + summary: Verify TOTP for MFA + description: Complete the multi-factor authentication by verifying a Time-based One-Time Password (TOTP). Returns a session if validation is successful. operationId: verifySignInMfaTotp tags: - authentication - - mfa requestBody: + description: MFA ticket and TOTP code for multi-factor authentication verification content: application/json: schema: @@ -308,16 +348,13 @@ paths: /signin/otp/email: post: - summary: >- - Sign in with a one time password sent to user's email. If the user doesn't exist, it will - be created. The options object is optional and can be used to configure the user's when - signing up a new user. It is ignored if the user already exists. + summary: Sign in with email OTP + description: Initiate email-based one-time password authentication. Sends an OTP to the specified email address. If the user doesn't exist, a new account will be created with the provided options. operationId: signInOTPEmail tags: - - signin - - signup - - otp + - authentication requestBody: + description: Email address and optional user options for OTP authentication content: application/json: schema: @@ -340,14 +377,13 @@ paths: /signin/otp/email/verify: post: - summary: >- - Verify OTP and return a session if validation is successful + summary: Verify email OTP + description: Complete email OTP authentication by verifying the one-time password. Returns a session if validation is successful. operationId: verifySignInOTPEmail tags: - - signin - - signup - - otp + - authentication requestBody: + description: OTP code and email address for verification content: application/json: schema: @@ -370,13 +406,13 @@ paths: /signin/passwordless/email: post: - summary: "Sign in with magic link email" - description: "Initiate passwordless authentication by sending a magic link to the user's email. If the user doesn't exist, a new account will be created with the provided options." + summary: Sign in with magic link email + description: Initiate passwordless authentication by sending a magic link to the user's email. If the user doesn't exist, a new account will be created with the provided options. operationId: signInPasswordlessEmail tags: - authentication - - passwordless requestBody: + description: Email address and optional user options for magic link authentication content: application/json: schema: @@ -399,16 +435,12 @@ paths: /signin/passwordless/sms: post: operationId: signInPasswordlessSms - summary: >- - Sign in with a one time password sent to user's phone number. If the user doesn't exist, it will be created. - The options object is optional and can be used to configure the user's when signing up a - new user. It is ignored if the user already exists. + summary: Sign in with SMS OTP + description: Initiate passwordless authentication by sending a one-time password to the user's phone number. If the user doesn't exist, a new account will be created with the provided options. tags: - - signin - - signup - - passwordless - - sms + - authentication requestBody: + description: Phone number and optional user options for SMS OTP authentication content: application/json: schema: @@ -422,18 +454,22 @@ paths: application/json: schema: $ref: "#/components/schemas/OKResponse" + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /signin/passwordless/sms/otp: post: operationId: verifySignInPasswordlessSms - summary: >- - Verify SMS OTP and return a session if validation is successful + summary: Verify SMS OTP + description: Complete passwordless SMS authentication by verifying the one-time password. Returns a session if validation is successful. tags: - - signin - - signup - - passwordless - - sms + - authentication requestBody: + description: Phone number and OTP code for SMS verification content: application/json: schema: @@ -446,16 +482,22 @@ paths: application/json: schema: $ref: "#/components/schemas/SignInPasswordlessSmsOtpResponse" + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /signin/pat: post: - summary: >- - Sign in with Personal Access Token (PAT) + summary: Sign in with Personal Access Token (PAT) + description: Authenticate using a Personal Access Token. PATs are long-lived tokens that can be used for programmatic access to the API. operationId: signInPAT tags: - - signin - - pat + - authentication requestBody: + description: Personal Access Token for authentication content: application/json: schema: @@ -478,20 +520,19 @@ paths: /signin/provider/{provider}: get: - summary: Sign in with an oauth2 provider + summary: Sign in with an OAuth2 provider + description: Initiate OAuth2 authentication flow with a social provider. Redirects the user to the provider's authorization page. operationId: signInProvider tags: - - signin - - signup - - link - - social - - oauth + - authentication parameters: - $ref: "#/components/parameters/SignInProvider" - name: allowedRoles in: query required: false description: Array of allowed roles for the user + style: form + explode: false schema: type: array items: @@ -527,13 +568,15 @@ paths: - name: metadata in: query required: false - description: Additional metadata for the user - schema: - type: object - additionalProperties: true - example: - firstName: John - lastName: Smith + description: Additional metadata for the user (JSON encoded string) + content: + application/json: + schema: + type: object + additionalProperties: true + example: + firstName: John + lastName: Smith - name: redirectTo in: query required: false @@ -557,17 +600,20 @@ paths: Location: $ref: "#/components/headers/RedirectLocation" content: {} + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /signin/provider/{provider}/callback: get: - summary: Callback for oauth2 provider + summary: OAuth2 provider callback endpoint + description: Handles the callback from OAuth2 providers after user authorization. Processes the authorization code and creates a user session. operationId: signInProviderCallbackGet tags: - - signin - - signup - - link - - social - - oauth + - authentication - excludeme parameters: - $ref: "#/components/parameters/SignInProvider" @@ -624,20 +670,24 @@ paths: Location: $ref: "#/components/headers/RedirectLocation" content: {} + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" post: - summary: Callback for oauth2 provider using form_post response mode + summary: OAuth2 provider callback endpoint (form_post) + description: Handles OAuth2 provider callbacks using form_post response mode. Used by providers like Apple that send data via POST instead of query parameters. operationId: signInProviderCallbackPost tags: - - signin - - signup - - link - - social - - oauth + - authentication - excludeme parameters: - $ref: "#/components/parameters/SignInProvider" requestBody: + description: OAuth2 provider callback data including authorization code, ID token, and state required: true content: application/x-www-form-urlencoded: @@ -681,6 +731,12 @@ paths: Location: $ref: "#/components/headers/RedirectLocation" content: {} + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /signin/webauthn: post: @@ -690,6 +746,7 @@ paths: The user must have previously registered a Webauthn credential. operationId: signInWebauthn requestBody: + description: Optional email address to help identify the user for WebAuthn authentication content: application/json: schema: @@ -703,10 +760,15 @@ paths: $ref: "#/components/schemas/PublicKeyCredentialRequestOptions" description: >- Challenge sent + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" tags: - - signin - - webauthn + - authentication /signin/webauthn/verify: post: @@ -716,6 +778,7 @@ paths: Returns a session if validation is successful. operationId: verifySignInWebauthn requestBody: + description: WebAuthn credential assertion response from the user's authenticator device content: application/json: schema: @@ -729,15 +792,20 @@ paths: $ref: "#/components/schemas/SessionPayload" description: >- Sign in successful + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" tags: - - signin - - webauthn - - verify + - authentication /signout: post: - summary: "Sign out" + summary: Sign out + description: End the current user session by invalidating refresh tokens. Optionally sign out from all devices. operationId: signOut tags: - session @@ -745,6 +813,7 @@ paths: - BearerAuth: [] - {} requestBody: + description: Sign-out options including refresh token and whether to sign out from all devices required: true content: application/json: @@ -766,13 +835,13 @@ paths: /signup/email-password: post: - summary: "Sign up with email and password" - description: "Register a new user account with email and password. Returns a session if email verification is not required, otherwise returns null session." + summary: Sign up with email and password + description: Register a new user account with email and password. Returns a session if email verification is not required, otherwise returns null session. operationId: signUpEmailPassword tags: - authentication - - email-and-password requestBody: + description: User registration information including email, password, and optional profile data content: application/json: schema: @@ -785,18 +854,12 @@ paths: schema: $ref: "#/components/schemas/SessionPayload" description: "Registration successful. If email verification is required, session will be null." - "403": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: "Signup mechanism is disabled" - "409": + default: content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - description: "User with given email already exists" + description: "An error occurred while processing the request" /signup/webauthn: post: @@ -806,6 +869,7 @@ paths: The user must not have an existing account. operationId: signUpWebauthn requestBody: + description: Email address and optional user options for WebAuthn registration content: application/json: schema: @@ -819,10 +883,15 @@ paths: $ref: "#/components/schemas/PublicKeyCredentialCreationOptions" description: >- Challenge sent + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" tags: - - signup - - webauthn + - authentication /signup/webauthn/verify: post: @@ -832,6 +901,7 @@ paths: Returns a session if validation is successful. operationId: verifySignUpWebauthn requestBody: + description: WebAuthn credential creation response and optional user profile information content: application/json: schema: @@ -845,20 +915,26 @@ paths: $ref: "#/components/schemas/SessionPayload" description: >- Sign up successful + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" tags: - - signup - - webauthn - - verify + - authentication + - verification /token: post: - summary: "Refresh access token" - description: "Generate a new JWT access token using a valid refresh token. The refresh token used will be revoked and a new one will be issued." + summary: Refresh access token + description: Generate a new JWT access token using a valid refresh token. The refresh token used will be revoked and a new one will be issued. operationId: refreshToken tags: - session requestBody: + description: Refresh token to exchange for a new access token content: application/json: schema: @@ -881,14 +957,15 @@ paths: /token/verify: post: summary: Verify JWT token + description: Verify the validity of a JWT access token. If no request body is provided, the Authorization header will be used for verification. operationId: verifyToken - description: If request body is not passed the authorization header will be used to be verified tags: - - General + - session security: - BearerAuth: [] - {} requestBody: + description: Optional JWT token to verify (if not provided, Authorization header will be used) content: application/json: schema: @@ -901,13 +978,20 @@ paths: type: string example: "OK" description: Valid JWT token + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /user: get: summary: Get user information + description: Retrieve the authenticated user's profile information including roles, metadata, and account status. operationId: getUser tags: - - User management + - user security: - BearerAuth: [] responses: @@ -917,17 +1001,24 @@ paths: schema: $ref: "#/components/schemas/User" description: User information + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /user/deanonymize: post: - summary: >- - Deanonymize an anonymous user in adding missing email or email+password, depending on the chosen authentication method. Will send a confirmation email if the server is configured to do so + summary: Deanonymize an anonymous user + description: Convert an anonymous user to a regular user by adding email and optionally password credentials. A confirmation email will be sent if the server is configured to do so. operationId: deanonymizeUser tags: - - anonymous + - authentication security: - BearerAuth: [] requestBody: + description: Authentication method and credentials to convert anonymous user to regular user content: application/json: schema: @@ -951,13 +1042,14 @@ paths: /user/email/change: post: summary: Change user email + description: Request to change the authenticated user's email address. A verification email will be sent to the new address to confirm the change. Requires elevated permissions. operationId: changeUserEmail tags: - user - - email security: - BearerAuthElevated: [] requestBody: + description: New email address and optional redirect URL for email change content: application/json: schema: @@ -981,11 +1073,12 @@ paths: /user/email/send-verification-email: post: summary: Send verification email + description: Send an email verification link to the specified email address. Used to verify email addresses for new accounts or email changes. operationId: sendVerificationEmail tags: - user - - email requestBody: + description: Email address and optional redirect URL for verification email content: application/json: schema: @@ -1008,15 +1101,15 @@ paths: /user/mfa: post: - summary: "Manage multi-factor authentication" - description: "Activate or deactivate multi-factor authentication for the authenticated user" + summary: Manage multi-factor authentication + description: Activate or deactivate multi-factor authentication for the authenticated user operationId: verifyChangeUserMfa tags: - - mfa - - user-management + - security security: - BearerAuth: [] requestBody: + description: TOTP verification code and MFA activation settings content: application/json: schema: @@ -1038,16 +1131,16 @@ paths: /user/password: post: - summary: >- - Change user password. The user must be authenticated or provide a ticket + summary: Change user password + description: Change the user's password. The user must be authenticated with elevated permissions or provide a valid password reset ticket. operationId: changeUserPassword tags: - user - - password security: - BearerAuthElevated: [] - {} requestBody: + description: New password and optional password reset ticket for authentication content: application/json: schema: @@ -1070,13 +1163,13 @@ paths: /user/password/reset: post: - summary: >- - Request a password reset. An email with a verification link will be sent to the user's address + summary: Request password reset + description: Request a password reset for a user account. An email with a verification link will be sent to the user's email address to complete the password reset process. operationId: sendPasswordResetEmail tags: - user - - password requestBody: + description: Email address and optional redirect URL for password reset content: application/json: schema: @@ -1100,10 +1193,10 @@ paths: /user/webauthn/add: post: summary: Initialize adding of a new webauthn security key + description: Start the process of adding a new WebAuthn security key to the user's account. Returns a challenge that must be completed by the user's authenticator device. Requires elevated permissions. operationId: addSecurityKey tags: - - user-management - - webauthn + - security security: - BearerAuthElevated: [] responses: @@ -1124,13 +1217,14 @@ paths: /user/webauthn/verify: post: summary: Verify adding of a new webauthn security key + description: Complete the process of adding a new WebAuthn security key by verifying the authenticator response. Requires elevated permissions. operationId: verifyAddSecurityKey tags: - - user-management - - webauthn + - security security: - BearerAuthElevated: [] requestBody: + description: WebAuthn credential creation response and optional security key nickname content: application/json: schema: @@ -1153,12 +1247,11 @@ paths: /verify: get: - summary: >- - Verify tickets created by email verification, email passwordless authentication (magic link), - or password reset + summary: Verify email and authentication tickets + description: Verify tickets created by email verification, magic link authentication, or password reset processes. Redirects the user to the appropriate destination upon successful verification. operationId: verifyTicket tags: - - verify + - verification parameters: - $ref: "#/components/parameters/TicketQuery" - $ref: "#/components/parameters/TicketTypeQuery" @@ -1170,11 +1263,17 @@ paths: Location: $ref: "#/components/headers/RedirectLocation" content: {} + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" /version: get: - summary: "Get service version" - description: "Retrieve version information about the authentication service" + summary: Get service version + description: Retrieve version information about the authentication service operationId: getVersion tags: - system @@ -1193,6 +1292,12 @@ paths: example: "1.2.3" required: - version + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" components: securitySchemes: @@ -1233,7 +1338,6 @@ components: description: Application identifier extension output credProps: $ref: "#/components/schemas/CredentialPropertiesOutput" - description: Credential properties extension output hmacCreateSecret: type: boolean description: HMAC secret extension output @@ -1273,7 +1377,6 @@ components: properties: clientDataJSON: $ref: "#/components/schemas/URLEncodedBase64" - description: The client data JSON transports: type: array items: @@ -1281,17 +1384,14 @@ components: description: The authenticator transports authenticatorData: $ref: "#/components/schemas/URLEncodedBase64" - description: The authenticator data publicKey: $ref: "#/components/schemas/URLEncodedBase64" - description: The credential public key publicKeyAlgorithm: type: integer format: int64 description: The public key algorithm identifier attestationObject: $ref: "#/components/schemas/URLEncodedBase64" - description: The attestation object containing authenticator data and attestation statement required: - clientDataJSON - attestationObject @@ -1302,16 +1402,13 @@ components: properties: authenticatorAttachment: $ref: "#/components/schemas/AuthenticatorAttachment" - description: The authenticator attachment preference requireResidentKey: type: boolean description: Whether the authenticator must create a client-side-resident public key credential source residentKey: $ref: "#/components/schemas/ResidentKeyRequirement" - description: The resident key requirement userVerification: $ref: "#/components/schemas/UserVerificationRequirement" - description: The user verification requirement AuthenticatorTransport: type: string @@ -1387,16 +1484,13 @@ components: description: The credential type represented by this object rawId: $ref: "#/components/schemas/URLEncodedBase64" - description: The raw credential ID clientExtensionResults: $ref: "#/components/schemas/AuthenticationExtensionsClientOutputs" - description: The client extension results authenticatorAttachment: type: string description: The authenticator attachment response: $ref: "#/components/schemas/AuthenticatorAssertionResponse" - description: The authenticator assertion response required: - id - type @@ -1419,16 +1513,13 @@ components: description: The credential type represented by this object rawId: $ref: "#/components/schemas/URLEncodedBase64" - description: The raw credential ID clientExtensionResults: $ref: "#/components/schemas/AuthenticationExtensionsClientOutputs" - description: The client extension results authenticatorAttachment: type: string description: The authenticator attachment response: $ref: "#/components/schemas/AuthenticatorAttestationResponse" - description: The authenticator attestation response required: - id - type @@ -1441,7 +1532,6 @@ components: properties: type: $ref: "#/components/schemas/CredentialType" - description: The credential type alg: type: integer description: The cryptographic algorithm identifier @@ -1627,13 +1717,10 @@ components: properties: rp: $ref: "#/components/schemas/RelyingPartyEntity" - description: The relying party entity user: $ref: "#/components/schemas/UserEntity" - description: The user entity challenge: $ref: "#/components/schemas/URLEncodedBase64" - description: A challenge intended to be used for generating the newly created credential's attestation object pubKeyCredParams: type: array items: @@ -1649,7 +1736,6 @@ components: description: A list of PublicKeyCredentialDescriptor objects representing public key credentials that are not acceptable to the caller authenticatorSelection: $ref: "#/components/schemas/AuthenticatorSelection" - description: The authenticator selection criteria hints: type: array items: @@ -1657,7 +1743,6 @@ components: description: Hints to help guide the user through the experience attestation: $ref: "#/components/schemas/ConveyancePreference" - description: The preferred attestation conveyance preference attestationFormats: type: array items: @@ -1665,7 +1750,6 @@ components: description: The preferred attestation statement formats extensions: $ref: "#/components/schemas/AuthenticationExtensions" - description: Additional parameters requesting additional processing by the client and authenticator required: - rp - user @@ -1678,10 +1762,8 @@ components: properties: type: $ref: "#/components/schemas/CredentialType" - description: The valid credential types id: $ref: "#/components/schemas/URLEncodedBase64" - description: CredentialID The ID of a credential to allow/disallow transports: type: array items: @@ -1709,7 +1791,6 @@ components: properties: challenge: $ref: "#/components/schemas/URLEncodedBase64" - description: A challenge intended to be used for generating the newly created credential's attestation object timeout: type: integer description: A time, in milliseconds, that the caller is willing to wait for the call to complete @@ -1723,7 +1804,6 @@ components: description: A list of CredentialDescriptor objects representing public key credentials acceptable to the caller userVerification: $ref: "#/components/schemas/UserVerificationRequirement" - description: A requirement for user verification for the operation hints: type: array items: @@ -1731,7 +1811,6 @@ components: description: Hints to help guide the user through the experience extensions: $ref: "#/components/schemas/AuthenticationExtensions" - description: Additional parameters requesting additional processing by the client and authenticator required: - challenge @@ -1797,7 +1876,6 @@ components: type: string user: $ref: "#/components/schemas/User" - description: "Information about the authenticated user" required: - accessToken - accessTokenExpiresIn @@ -1811,7 +1889,6 @@ components: properties: session: $ref: "#/components/schemas/Session" - description: "User session data. Null if authentication requires additional steps." SignInAnonymousRequest: type: object @@ -1861,10 +1938,8 @@ components: properties: session: $ref: "#/components/schemas/Session" - description: "User session if authentication was successful. Null if MFA challenge is required." mfa: $ref: "#/components/schemas/MFAChallengePayload" - description: "MFA challenge if two-factor authentication is required" SignInIdTokenRequest: type: object @@ -2053,7 +2128,6 @@ components: type: string options: $ref: "#/components/schemas/SignUpOptions" - description: "Optional configuration for the new user account" required: - email - password @@ -2234,36 +2308,6 @@ components: - phoneNumberVerified - roles - UserAddSecurityKeyVerifyRequest: - type: object - additionalProperties: false - properties: - credential: - type: object - additionalProperties: true - x-go-type-import: - name: protocol - path: github.com/go-webauthn/webauthn/protocol - x-go-type: protocol.CredentialCreationResponse - nickname: - type: string - description: Optional nickname for the security key - required: - - credential - - UserAddSecurityKeyVerifyResponse: - type: object - additionalProperties: false - properties: - id: - type: string - description: ID of the newly added security key - nickname: - type: string - description: Nickname of the security key - required: - - id - UserDeanonymizeRequest: type: object additionalProperties: false @@ -2404,7 +2448,6 @@ components: properties: credential: $ref: "#/components/schemas/CredentialCreationResponse" - description: The credential created during registration nickname: type: string description: Optional nickname for the security key @@ -2493,7 +2536,7 @@ components: - signinPasswordless - passwordReset description: Type of the ticket - example: email-verification + example: emailVerify deprecated: true headers: diff --git a/packages/nhost-js/src/auth/client.ts b/packages/nhost-js/src/auth/client.ts index b87a6ffd..d92cdb13 100644 --- a/packages/nhost-js/src/auth/client.ts +++ b/packages/nhost-js/src/auth/client.ts @@ -1249,36 +1249,6 @@ export interface User { activeMfaType?: string; } -/** - * - @property credential (`Record`) - - @property nickname? (`string`) - Optional nickname for the security key*/ -export interface UserAddSecurityKeyVerifyRequest { - /** - * - */ - credential: Record; - /** - * Optional nickname for the security key - */ - nickname?: string; -} - -/** - * - @property id (`string`) - ID of the newly added security key - @property nickname? (`string`) - Nickname of the security key*/ -export interface UserAddSecurityKeyVerifyResponse { - /** - * ID of the newly added security key - */ - id: string; - /** - * Nickname of the security key - */ - nickname?: string; -} - /** * Which sign-in method to use */ @@ -1561,7 +1531,7 @@ export interface GetVersionResponse200 { @property locale? (string) - A two-characters locale - @property metadata? (Record) - Additional metadata for the user + @property metadata? (Record) - Additional metadata for the user (JSON encoded string) @property redirectTo? (string) - URI to redirect to @@ -1589,7 +1559,7 @@ export interface SignInProviderParams { */ locale?: string; /** - * Additional metadata for the user + * Additional metadata for the user (JSON encoded string) */ metadata?: Record; @@ -1641,7 +1611,7 @@ export interface Client { pushChainFunction(chainFunction: ChainFunction): void; /** Summary: Get public keys for JWT verification in JWK Set format - + Retrieve the JSON Web Key Set (JWKS) containing public keys used to verify JWT signatures. This endpoint is used by clients to validate access tokens. This method may return different T based on the response code: - 200: JWKSet @@ -1691,7 +1661,7 @@ export interface Client { /** Summary: Link a user account with the provider's account using an id token - + Link the authenticated user's account with an external OAuth provider account using an ID token. Requires elevated permissions. This method may return different T based on the response code: - 200: OKResponse @@ -1714,7 +1684,7 @@ export interface Client { /** Summary: Create a Personal Access Token (PAT) - + Generate a new Personal Access Token for programmatic API access. PATs are long-lived tokens that can be used instead of regular authentication for automated systems. Requires elevated permissions. This method may return different T based on the response code: - 200: CreatePATResponse @@ -1726,7 +1696,7 @@ export interface Client { /** Summary: Sign in anonymously - + Create an anonymous user session without providing credentials. Anonymous users can be converted to regular users later via the deanonymize endpoint. This method may return different T based on the response code: - 200: SessionPayload @@ -1749,8 +1719,8 @@ export interface Client { ): Promise>; /** - Summary: Sign in with in an id token - + Summary: Sign in with an ID token + Authenticate using an ID token from a supported OAuth provider (Apple or Google). Creates a new user account if one doesn't exist. This method may return different T based on the response code: - 200: SessionPayload @@ -1773,8 +1743,8 @@ export interface Client { ): Promise>; /** - Summary: Sign in with a one time password sent to user's email. If the user doesn't exist, it will be created. The options object is optional and can be used to configure the user's when signing up a new user. It is ignored if the user already exists. - + Summary: Sign in with email OTP + Initiate email-based one-time password authentication. Sends an OTP to the specified email address. If the user doesn't exist, a new account will be created with the provided options. This method may return different T based on the response code: - 200: OKResponse @@ -1785,8 +1755,8 @@ export interface Client { ): Promise>; /** - Summary: Verify OTP and return a session if validation is successful - + Summary: Verify email OTP + Complete email OTP authentication by verifying the one-time password. Returns a session if validation is successful. This method may return different T based on the response code: - 200: SignInOTPEmailVerifyResponse @@ -1809,8 +1779,8 @@ export interface Client { ): Promise>; /** - Summary: Sign in with a one time password sent to user's phone number. If the user doesn't exist, it will be created. The options object is optional and can be used to configure the user's when signing up a new user. It is ignored if the user already exists. - + Summary: Sign in with SMS OTP + Initiate passwordless authentication by sending a one-time password to the user's phone number. If the user doesn't exist, a new account will be created with the provided options. This method may return different T based on the response code: - 200: OKResponse @@ -1821,8 +1791,8 @@ export interface Client { ): Promise>; /** - Summary: Verify SMS OTP and return a session if validation is successful - + Summary: Verify SMS OTP + Complete passwordless SMS authentication by verifying the one-time password. Returns a session if validation is successful. This method may return different T based on the response code: - 200: SignInPasswordlessSmsOtpResponse @@ -1834,7 +1804,7 @@ export interface Client { /** Summary: Sign in with Personal Access Token (PAT) - + Authenticate using a Personal Access Token. PATs are long-lived tokens that can be used for programmatic access to the API. This method may return different T based on the response code: - 200: SessionPayload @@ -1845,8 +1815,8 @@ export interface Client { ): Promise>; /** - Summary: Sign in with an oauth2 provider - + Summary: Sign in with an OAuth2 provider + Initiate OAuth2 authentication flow with a social provider. Redirects the user to the provider's authorization page. As this method is a redirect, it returns a URL string instead of a Promise */ @@ -1882,7 +1852,7 @@ export interface Client { /** Summary: Sign out - + End the current user session by invalidating refresh tokens. Optionally sign out from all devices. This method may return different T based on the response code: - 200: OKResponse @@ -1898,8 +1868,6 @@ export interface Client { This method may return different T based on the response code: - 200: SessionPayload - - 403: ErrorResponse - - 409: ErrorResponse */ signUpEmailPassword( body: SignUpEmailPasswordRequest, @@ -1944,7 +1912,7 @@ export interface Client { /** Summary: Verify JWT token - If request body is not passed the authorization header will be used to be verified + Verify the validity of a JWT access token. If no request body is provided, the Authorization header will be used for verification. This method may return different T based on the response code: - 200: string @@ -1956,7 +1924,7 @@ export interface Client { /** Summary: Get user information - + Retrieve the authenticated user's profile information including roles, metadata, and account status. This method may return different T based on the response code: - 200: User @@ -1964,8 +1932,8 @@ export interface Client { getUser(options?: RequestInit): Promise>; /** - Summary: Deanonymize an anonymous user in adding missing email or email+password, depending on the chosen authentication method. Will send a confirmation email if the server is configured to do so - + Summary: Deanonymize an anonymous user + Convert an anonymous user to a regular user by adding email and optionally password credentials. A confirmation email will be sent if the server is configured to do so. This method may return different T based on the response code: - 200: OKResponse @@ -1977,7 +1945,7 @@ export interface Client { /** Summary: Change user email - + Request to change the authenticated user's email address. A verification email will be sent to the new address to confirm the change. Requires elevated permissions. This method may return different T based on the response code: - 200: OKResponse @@ -1989,7 +1957,7 @@ export interface Client { /** Summary: Send verification email - + Send an email verification link to the specified email address. Used to verify email addresses for new accounts or email changes. This method may return different T based on the response code: - 200: OKResponse @@ -2012,8 +1980,8 @@ export interface Client { ): Promise>; /** - Summary: Change user password. The user must be authenticated or provide a ticket - + Summary: Change user password + Change the user's password. The user must be authenticated with elevated permissions or provide a valid password reset ticket. This method may return different T based on the response code: - 200: OKResponse @@ -2024,8 +1992,8 @@ export interface Client { ): Promise>; /** - Summary: Request a password reset. An email with a verification link will be sent to the user's address - + Summary: Request password reset + Request a password reset for a user account. An email with a verification link will be sent to the user's email address to complete the password reset process. This method may return different T based on the response code: - 200: OKResponse @@ -2037,7 +2005,7 @@ export interface Client { /** Summary: Initialize adding of a new webauthn security key - + Start the process of adding a new WebAuthn security key to the user's account. Returns a challenge that must be completed by the user's authenticator device. Requires elevated permissions. This method may return different T based on the response code: - 200: PublicKeyCredentialCreationOptions @@ -2048,7 +2016,7 @@ export interface Client { /** Summary: Verify adding of a new webauthn security key - + Complete the process of adding a new WebAuthn security key by verifying the authenticator response. Requires elevated permissions. This method may return different T based on the response code: - 200: VerifyAddSecurityKeyResponse @@ -2059,8 +2027,8 @@ export interface Client { ): Promise>; /** - Summary: Verify tickets created by email verification, email passwordless authentication (magic link), or password reset - + Summary: Verify email and authentication tickets + Verify tickets created by email verification, magic link authentication, or password reset processes. Redirects the user to the appropriate destination upon successful verification. As this method is a redirect, it returns a URL string instead of a Promise */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9390d217..97d2729f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,10 @@ overrides: esbuild@<=0.24.2: '>=0.25.0' tar-fs@>=3.0.0 <3.0.9: '>=3.0.9' next@>=15.3.0 <15.3.3: '>=15.3.3' + form-data@>=4.0.0 <4.0.4: '>=4.0.4' + '@eslint/plugin-kit@<0.3.4': '>=0.3.4' + on-headers@<1.1.0: '>=1.1.0' + tmp@<=0.2.3: '>=0.2.4' importers: @@ -1179,6 +1183,10 @@ packages: resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1195,8 +1203,8 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.8': - resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@expo/cli@0.24.14': @@ -4570,8 +4578,8 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data@4.0.2: - resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} format@0.2.2: @@ -6317,8 +6325,8 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} - on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} once@1.4.0: @@ -6365,10 +6373,6 @@ packages: resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -7563,9 +7567,9 @@ packages: title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tmp@0.2.4: + resolution: {integrity: sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==} + engines: {node: '>=14.14'} tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -9076,6 +9080,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 @@ -9095,15 +9103,15 @@ snapshots: '@eslint/json@0.12.0': dependencies: '@eslint/core': 0.12.0 - '@eslint/plugin-kit': 0.2.8 + '@eslint/plugin-kit': 0.3.4 '@humanwhocodes/momoa': 3.3.8 natural-compare: 1.4.0 '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.8': + '@eslint/plugin-kit@0.3.4': dependencies: - '@eslint/core': 0.13.0 + '@eslint/core': 0.15.1 levn: 0.4.1 '@expo/cli@0.24.14(graphql@16.11.0)': @@ -12010,7 +12018,7 @@ snapshots: axios@1.9.0: dependencies: follow-redirects: 1.15.9 - form-data: 4.0.2 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -12583,7 +12591,7 @@ snapshots: compressible: 2.0.18 debug: 2.6.9 negotiator: 0.6.4 - on-headers: 1.0.2 + on-headers: 1.1.0 safe-buffer: 5.2.1 vary: 1.1.2 transitivePeerDependencies: @@ -13157,7 +13165,7 @@ snapshots: '@eslint/core': 0.13.0 '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.26.0 - '@eslint/plugin-kit': 0.2.8 + '@eslint/plugin-kit': 0.3.4 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -13566,7 +13574,7 @@ snapshots: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 - tmp: 0.0.33 + tmp: 0.2.4 extract-zip@2.0.1: dependencies: @@ -13738,11 +13746,12 @@ snapshots: form-data-encoder@2.1.4: {} - form-data@4.0.2: + form-data@4.0.4: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 + hasown: 2.0.2 mime-types: 2.1.35 format@0.2.2: {} @@ -16148,7 +16157,7 @@ snapshots: dependencies: ee-first: 1.1.1 - on-headers@1.0.2: {} + on-headers@1.1.0: {} once@1.4.0: dependencies: @@ -16228,8 +16237,6 @@ snapshots: strip-ansi: 7.1.0 wcwidth: 1.0.1 - os-tmpdir@1.0.2: {} - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -17801,9 +17808,7 @@ snapshots: dependencies: tslib: 2.8.1 - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 + tmp@0.2.4: {} tmpl@1.0.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a057d15c..7d3b7fd4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,3 +12,7 @@ overrides: esbuild@<=0.24.2: '>=0.25.0' tar-fs@>=3.0.0 <3.0.9: '>=3.0.9' next@>=15.3.0 <15.3.3: '>=15.3.3' + form-data@>=4.0.0 <4.0.4: '>=4.0.4' + '@eslint/plugin-kit@<0.3.4': '>=0.3.4' + on-headers@<1.1.0: '>=1.1.0' + tmp@<=0.2.3: '>=0.2.4' diff --git a/tools/codegen/processor/intermediate_test.go b/tools/codegen/processor/intermediate_test.go index 3e56ec15..abc4d59b 100644 --- a/tools/codegen/processor/intermediate_test.go +++ b/tools/codegen/processor/intermediate_test.go @@ -50,6 +50,9 @@ func TestInterMediateRepresentationRender(t *testing.T) { { name: "methods_ref.yaml", }, + { + name: "content.yaml", + }, } for _, tc := range cases { diff --git a/tools/codegen/processor/methods.go b/tools/codegen/processor/methods.go index 96850664..1709a75e 100644 --- a/tools/codegen/processor/methods.go +++ b/tools/codegen/processor/methods.go @@ -286,12 +286,32 @@ func getMethodParameters( p: p, } } else { - t2, tt, err := GetType(param.Schema, method+format.Title(param.Name), p, false) - if err != nil { - return nil, nil, fmt.Errorf("failed to get type for parameter %s: %w", param.Name, err) + switch { + case param.Schema != nil: + t2, tt, err := GetType(param.Schema, method+format.Title(param.Name), p, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to get type for parameter %s: %w", param.Name, err) + } + types = append(types, tt...) + t = t2 + case param.Content != nil: + jsonMediaType, ok := param.Content.Get("application/json") + if !ok { + return nil, nil, fmt.Errorf( //nolint:err113 + "parameter %s in operation %s has no application/json content defined", + param.Name, + operation.OperationId, + ) + } + t2, tt, err := GetType(jsonMediaType.Schema, method+format.Title(param.Name), p, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to get type for parameter %s: %w", param.Name, err) + } + types = append(types, tt...) + t = t2 + default: + return nil, nil, fmt.Errorf("parameter %s in operation %s has no schema or content defined", param.Name, operation.OperationId) //nolint:goerr113,lll } - types = append(types, tt...) - t = t2 } params[i] = &Parameter{ name: param.Name, diff --git a/tools/codegen/processor/testdata/content.yaml b/tools/codegen/processor/testdata/content.yaml new file mode 100644 index 00000000..eedfde24 --- /dev/null +++ b/tools/codegen/processor/testdata/content.yaml @@ -0,0 +1,144 @@ +openapi: "3.0.0" + +paths: + /signin/provider/{provider}: + get: + summary: Sign in with an OAuth2 provider + description: Initiate OAuth2 authentication flow with a social provider. Redirects the user to the provider's authorization page. + operationId: signInProvider + tags: + - authentication + parameters: + - $ref: "#/components/parameters/SignInProvider" + - name: allowedRoles + in: query + required: false + description: Array of allowed roles for the user + style: form + explode: false + schema: + type: array + items: + type: string + example: + - me + - user + - name: defaultRole + in: query + required: false + description: Default role for the user + schema: + type: string + example: user + - name: displayName + in: query + required: false + description: Display name for the user + schema: + type: string + pattern: ^[\p{L}\p{N}\p{S} ,.'-]+$ + maxLength: 32 + example: John Smith + - name: locale + in: query + required: false + description: A two-characters locale + schema: + type: string + maxLength: 2 + minLength: 2 + example: en + - name: metadata + in: query + required: false + description: Additional metadata for the user (JSON encoded string) + content: + application/json: + schema: + type: object + additionalProperties: true + example: + firstName: John + lastName: Smith + - name: redirectTo + in: query + required: false + description: URI to redirect to + schema: + type: string + format: uri + example: https://my-app.com/catch-redirection + - name: connect + in: query + required: false + description: >- + If set, this means that the user is already authenticated and wants to link their account. This needs to be a valid JWT access token. + schema: + type: string + + responses: + "302": + description: Redirect to social provider + headers: + Location: + $ref: "#/components/headers/RedirectLocation" + content: {} + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: "An error occurred while processing the request" + +components: + parameters: + SignInProvider: + in: path + name: provider + required: true + description: The name of the social provider + schema: + type: string + enum: + - apple + - github + - google + - linkedin + - discord + - spotify + - twitch + - gitlab + - bitbucket + - workos + - azuread + - strava + - facebook + - windowslive + - twitter + deprecated: true + + headers: + RedirectLocation: + description: URL to redirect to + schema: + type: string + format: uri + required: true + + schemas: + ErrorResponse: + type: object + description: "Error information returned by the API." + properties: + error: + type: object + additionalProperties: false + description: "Error details." + properties: + message: + type: string + description: "Human-readable error message." + example: "File not found" + required: + - message + additionalProperties: false diff --git a/tools/codegen/processor/testdata/content.yaml.ts b/tools/codegen/processor/testdata/content.yaml.ts new file mode 100644 index 00000000..0ef322d5 --- /dev/null +++ b/tools/codegen/processor/testdata/content.yaml.ts @@ -0,0 +1,149 @@ +/** + * This file is auto-generated. Do not edit manually. + */ + +import { FetchError, createEnhancedFetch } from "../fetch"; +import type { ChainFunction, FetchResponse } from "../fetch"; + +/** + * Error details. + @property message (`string`) - Human-readable error message. + * Example - `"File not found"`*/ +export interface ErrorResponseError { + /** + * Human-readable error message. + * Example - `"File not found"` + */ + message: string, +}; + + +/** + * Error information returned by the API. + @property error? (`ErrorResponseError`) - Error details.*/ +export interface ErrorResponse { + /** + * Error details. + */ + error?: ErrorResponseError, +}; + + +/** + * + */ +export type SignInProvider = "apple" | "github" | "google" | "linkedin" | "discord" | "spotify" | "twitch" | "gitlab" | "bitbucket" | "workos" | "azuread" | "strava" | "facebook" | "windowslive" | "twitter"; + +/** + * Parameters for the signInProvider method. + @property allowedRoles? (string[]) - Array of allowed roles for the user + + @property defaultRole? (string) - Default role for the user + + @property displayName? (string) - Display name for the user + + @property locale? (string) - A two-characters locale + + @property metadata? (Record) - Additional metadata for the user (JSON encoded string) + + @property redirectTo? (string) - URI to redirect to + + @property connect? (string) - If set, this means that the user is already authenticated and wants to link their account. This needs to be a valid JWT access token. + */ +export interface SignInProviderParams { + /** + * Array of allowed roles for the user + + */ + allowedRoles?: string[]; + /** + * Default role for the user + + */ + defaultRole?: string; + /** + * Display name for the user + + */ + displayName?: string; + /** + * A two-characters locale + + */ + locale?: string; + /** + * Additional metadata for the user (JSON encoded string) + + */ + metadata?: Record; + /** + * URI to redirect to + + */ + redirectTo?: string; + /** + * If set, this means that the user is already authenticated and wants to link their account. This needs to be a valid JWT access token. + + */ + connect?: string; +} + + +export interface Client { + baseURL: string; + pushChainFunction(chainFunction: ChainFunction): void; + /** + Summary: Sign in with an OAuth2 provider + Initiate OAuth2 authentication flow with a social provider. Redirects the user to the provider's authorization page. + + As this method is a redirect, it returns a URL string instead of a Promise + */ + signInProviderURL( + provider: SignInProvider, + params?: SignInProviderParams, + options?: RequestInit, + ): string; +}; + + +export const createAPIClient = ( + baseURL: string, + chainFunctions: ChainFunction[] = [], +): Client => { + let fetch = createEnhancedFetch(chainFunctions); + + const pushChainFunction = (chainFunction: ChainFunction) => { + chainFunctions.push(chainFunction); + fetch = createEnhancedFetch(chainFunctions); + }; + const signInProviderURL = ( + provider: SignInProvider, + params?: SignInProviderParams, + ): string => { + const encodedParameters = + params && + Object.entries(params) + .map(([key, value]) => { + const stringValue = Array.isArray(value) + ? value.join(',') + : typeof value === 'object' + ? JSON.stringify(value) + : (value as string) + return `${key}=${encodeURIComponent(stringValue)}` + }) + .join('&') + + const url = + encodedParameters + ? baseURL + `/signin/provider/${provider}?${encodedParameters}` + : baseURL + `/signin/provider/${provider}`; + return url; + }; + + + return { + baseURL, + pushChainFunction, + signInProviderURL, + }; +}; diff --git a/tools/codegen/processor/typescript/templates/types.tmpl b/tools/codegen/processor/typescript/templates/types.tmpl index f1562101..3210406c 100644 --- a/tools/codegen/processor/typescript/templates/types.tmpl +++ b/tools/codegen/processor/typescript/templates/types.tmpl @@ -21,7 +21,7 @@ {{- if .Description }} {{- .Description }} {{ end }} - {{- if .Schema.Schema.Description }} + {{- if and .Schema .Schema.Schema .Schema.Schema.Description }} * {{ .Schema.Schema.Description }} {{- end }} {{- end -}} From f5521c1e986f3b7c4de28de0bf44cbc9f6008e5b Mon Sep 17 00:00:00 2001 From: David Barroso Date: Mon, 11 Aug 2025 14:07:27 +0200 Subject: [PATCH 2/7] asd --- backend/nhost/nhost.toml | 6 +- docs/reference/javascript/nhost-js/auth.mdx | 2 +- .../reference/javascript/nhost-js/storage.mdx | 324 +++++--- flake.lock | 19 +- flake.nix | 2 +- packages/nhost-js/api/storage.yaml | 757 +++++++++++------- .../nhost-js/src/__tests__/storage.test.ts | 19 +- .../src/storage/__tests__/docstrings.test.ts | 2 + packages/nhost-js/src/storage/client.ts | 574 +++++++------ .../processor/testdata/methods_ref.yaml.ts | 12 +- .../typescript/templates/client.tmpl | 12 +- 11 files changed, 993 insertions(+), 736 deletions(-) diff --git a/backend/nhost/nhost.toml b/backend/nhost/nhost.toml index ce6cb54c..3dc2163a 100644 --- a/backend/nhost/nhost.toml +++ b/backend/nhost/nhost.toml @@ -31,7 +31,7 @@ httpPoolSize = 100 version = 20 [auth] -version = '0.40.1' +version = '0.41.1' [auth.elevatedPrivileges] mode = 'disabled' @@ -182,7 +182,7 @@ limit = 1000 interval = '1m' [postgres] -version = '17.4-20250530-1' +version = '17.5-20250728-1' [postgres.resources] [postgres.resources.storage] @@ -191,7 +191,7 @@ capacity = 1 [provider] [storage] -version = '0.7.2' +version = '0.8.0-beta1' [observability] [observability.grafana] diff --git a/docs/reference/javascript/nhost-js/auth.mdx b/docs/reference/javascript/nhost-js/auth.mdx index eb24bb9f..59bc28d6 100644 --- a/docs/reference/javascript/nhost-js/auth.mdx +++ b/docs/reference/javascript/nhost-js/auth.mdx @@ -2280,7 +2280,7 @@ A two-characters locale ##### metadata? ```ts -optional metadata: string; +optional metadata: Record; ``` Additional metadata for the user (JSON encoded string) diff --git a/docs/reference/javascript/nhost-js/storage.mdx b/docs/reference/javascript/nhost-js/storage.mdx index dd1245c4..0b826283 100644 --- a/docs/reference/javascript/nhost-js/storage.mdx +++ b/docs/reference/javascript/nhost-js/storage.mdx @@ -79,30 +79,30 @@ import { createClient } from "@nhost/nhost-js"; import { type ErrorResponse } from "@nhost/nhost-js/storage"; import { type FetchError } from "@nhost/nhost-js/fetch"; -const nhost = createClient({ - subdomain, - region, -}); - -try { - await nhost.storage.uploadFiles({ - "file[]": [new File(["test1"], "file-1", { type: "text/plain" })], + const nhost = createClient({ + subdomain, + region, }); -} catch (error) { - const err = error as FetchError; - console.log("Error:", err); - // Error: { - // body: { error: { message: 'you are not authorized' } }, - // status: 403, - // headers: { - // 'content-length': '46', - // 'content-type': 'application/json; charset=utf-8', - // date: 'Mon, 12 May 2025 08:18:52 GMT' - // } - // } - - // error handling... -} + + try { + await nhost.storage.uploadFiles({ + "file[]": [new File(["test1"], "file-1", { type: "text/plain" })], + }); + } catch (error) { + const err = error as FetchError; + console.log("Error:", err); + // Error: { + // body: { error: { message: 'you are not authorized' } }, + // status: 403, + // headers: { + // 'content-length': '46', + // 'content-type': 'application/json; charset=utf-8', + // date: 'Mon, 12 May 2025 08:18:52 GMT' + // } + // } + + // error handling... + processedFiles: [], ``` This type extends the standard `Error` type so if you want to just log the error you can @@ -113,24 +113,23 @@ import { createClient } from "@nhost/nhost-js"; import { type ErrorResponse } from "@nhost/nhost-js/storage"; import { type FetchError } from "@nhost/nhost-js/fetch"; -const nhost = createClient({ - subdomain, - region, -}); + const region = "local"; -try { - await nhost.storage.uploadFiles({ - "file[]": [new File(["test1"], "file-1", { type: "text/plain" })], + const nhost = createClient({ + subdomain, + region, }); -} catch (error) { - if (!(error instanceof Error)) { - throw error; // Re-throw if it's not an Error - } - console.log("Error:", error.message); - // Error: you are not authorized - // error handling... -} + try { + await nhost.storage.uploadFiles({ + + expect(true).toBe(false); // This should not be reached + } catch (error) { + if (!(error instanceof Error)) { + throw error; // Re-throw if it's not an Error + } + + console.log("Error:", error.message); ``` ## Interfaces @@ -159,7 +158,6 @@ Broken metadata is defined as metadata that has isUploaded = true but there is n This method may return different T based on the response code: - 200: DeleteBrokenMetadataResponse200 -- 400: ErrorResponse ###### Parameters @@ -183,7 +181,6 @@ Permanently delete a file from storage. This removes both the file content and i This method may return different T based on the response code: - 204: void -- 400: ErrorResponse ###### Parameters @@ -208,7 +205,6 @@ Orphaned files are files that are present in the storage but have no associated This method may return different T based on the response code: - 200: DeleteOrphanedFilesResponse200 -- 400: ErrorResponse ###### Parameters @@ -235,8 +231,8 @@ Retrieve and download the complete file content. Supports conditional requests, This method may return different T based on the response code: - 200: void +- 206: void - 304: void -- 400: void - 412: void ###### Parameters @@ -267,7 +263,6 @@ This method may return different T based on the response code: - 200: void - 304: void -- 400: void - 412: void ###### Parameters @@ -282,10 +277,10 @@ This method may return different T based on the response code: `Promise`<[`FetchResponse`](fetch#fetchresponse)<`void`>> -##### getPresignedURL() +##### getFilePresignedURL() ```ts -getPresignedURL(id: string, options?: RequestInit): Promise>; +getFilePresignedURL(id: string, options?: RequestInit): Promise>; ``` Summary: Retrieve presigned URL to retrieve the file @@ -295,7 +290,6 @@ determined by bucket configuration This method may return different T based on the response code: - 200: PresignedURLResponse -- 400: ErrorResponse ###### Parameters @@ -343,7 +337,6 @@ Broken metadata is defined as metadata that has isUploaded = true but there is n This method may return different T based on the response code: - 200: ListBrokenMetadataResponse200 -- 400: ErrorResponse ###### Parameters @@ -367,7 +360,6 @@ That is, metadata that has isUploaded = false. This is an admin operation that r This method may return different T based on the response code: - 200: ListFilesNotUploadedResponse200 -- 400: ErrorResponse ###### Parameters @@ -391,7 +383,6 @@ Orphaned files are files that are present in the storage but have no associated This method may return different T based on the response code: - 200: ListOrphanedFilesResponse200 -- 400: ErrorResponse ###### Parameters @@ -440,7 +431,6 @@ Each step is atomic, but if a step fails, previous steps will not be automatical This method may return different T based on the response code: - 200: FileMetadata -- 400: ErrorResponse ###### Parameters @@ -466,7 +456,6 @@ Upload one or more files to a specified bucket. Supports batch uploading with op This method may return different T based on the response code: - 201: UploadFilesResponse201 -- 400: ErrorResponse ###### Parameters @@ -527,6 +516,64 @@ Error details. #### Properties +##### data? + +```ts +optional data: Record; +``` + +Additional data related to the error, if any. + +##### message + +```ts +message: string; +``` + +(`string`) - Human-readable error message. + +- Example - `"File not found"` + +--- + +### ErrorResponseWithProcessedFiles + +Error information returned by the API. + +#### Properties + +##### error? + +```ts +optional error: ErrorResponseWithProcessedFilesError; +``` + +Error details. + +##### processedFiles? + +```ts +optional processedFiles: FileMetadata[]; +``` + +List of files that were successfully processed before the error occurred. + +--- + +### ErrorResponseWithProcessedFilesError + +Error details. + +#### Properties + +##### data? + +```ts +optional data: Record; +``` + +Additional data related to the error, if any. + ##### message ```ts @@ -545,51 +592,56 @@ Comprehensive metadata information about a file in storage. #### Properties -##### bucketId? +##### bucketId ```ts -optional bucketId: string; +bucketId: string; ``` -ID of the bucket containing the file. -Example - `"users-bucket"` +(`string`) - ID of the bucket containing the file. + +- Example - `"users-bucket"` -##### createdAt? +##### createdAt ```ts -optional createdAt: string; +createdAt: string; ``` -Timestamp when the file was created. -Example - `"2023-01-15T12:34:56Z"` -Format - date-time +(`string`) - Timestamp when the file was created. + +- Example - `"2023-01-15T12:34:56Z"` +- Format - date-time -##### etag? +##### etag ```ts -optional etag: string; +etag: string; ``` -Entity tag for cache validation. -Example - `"\"a1b2c3d4e5f6\""` +(`string`) - Entity tag for cache validation. -##### id? +- Example - `"\"a1b2c3d4e5f6\""` + +##### id ```ts -optional id: string; +id: string; ``` -Unique identifier for the file. -Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` +(`string`) - Unique identifier for the file. -##### isUploaded? +- Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` + +##### isUploaded ```ts -optional isUploaded: boolean; +isUploaded: boolean; ``` -Whether the file has been successfully uploaded. -Example - `true` +(`boolean`) - Whether the file has been successfully uploaded. + +- Example - `true` ##### metadata? @@ -600,42 +652,47 @@ optional metadata: Record; Custom metadata associated with the file. Example - `{"alt":"Profile picture","category":"avatar"}` -##### mimeType? +##### mimeType ```ts -optional mimeType: string; +mimeType: string; ``` -MIME type of the file. -Example - `"image/jpeg"` +(`string`) - MIME type of the file. -##### name? +- Example - `"image/jpeg"` + +##### name ```ts -optional name: string; +name: string; ``` -Name of the file including extension. -Example - `"profile-picture.jpg"` +(`string`) - Name of the file including extension. + +- Example - `"profile-picture.jpg"` -##### size? +##### size ```ts -optional size: number; +size: number; ``` -Size of the file in bytes. -Example - `245678` +(`number`) - Size of the file in bytes. -##### updatedAt? +- Example - `245678` +- Format - int64 + +##### updatedAt ```ts -optional updatedAt: string; +updatedAt: string; ``` -Timestamp when the file was last updated. -Example - `"2023-01-16T09:45:32Z"` -Format - date-time +(`string`) - Timestamp when the file was last updated. + +- Example - `"2023-01-16T09:45:32Z"` +- Format - date-time ##### uploadedByUserId? @@ -654,41 +711,45 @@ Basic information about a file in storage. #### Properties -##### bucketId? +##### bucketId ```ts -optional bucketId: string; +bucketId: string; ``` -ID of the bucket containing the file. -Example - `"users-bucket"` +(`string`) - ID of the bucket containing the file. -##### id? +- Example - `"users-bucket"` + +##### id ```ts -optional id: string; +id: string; ``` -Unique identifier for the file. -Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` +(`string`) - Unique identifier for the file. + +- Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` -##### isUploaded? +##### isUploaded ```ts -optional isUploaded: boolean; +isUploaded: boolean; ``` -Whether the file has been successfully uploaded. -Example - `true` +(`boolean`) - Whether the file has been successfully uploaded. -##### name? +- Example - `true` + +##### name ```ts -optional name: string; +name: string; ``` -Name of the file including extension. -Example - `"profile-picture.jpg"` +(`string`) - Name of the file including extension. + +- Example - `"profile-picture.jpg"` --- @@ -709,11 +770,13 @@ Blur the image using this sigma value. Only applies to image files ##### f? ```ts -optional f: HeadF; +optional f: OutputImageFormat; ``` Output format for image files. Use 'auto' for content negotiation based on Accept header +- Output format for image files. Use 'auto' for content negotiation based on Accept header + ##### h? ```ts @@ -757,11 +820,13 @@ Blur the image using this sigma value. Only applies to image files ##### f? ```ts -optional f: GetF; +optional f: OutputImageFormat; ``` Output format for image files. Use 'auto' for content negotiation based on Accept header +- Output format for image files. Use 'auto' for content negotiation based on Accept header + ##### h? ```ts @@ -830,23 +895,25 @@ Contains a presigned URL for direct file operations. #### Properties -##### expiration? +##### expiration ```ts -optional expiration: number; +expiration: number; ``` -The time in seconds until the URL expires. -Example - `3600` +(`number`) - The time in seconds until the URL expires. -##### url? +- Example - `3600` + +##### url ```ts -optional url: string; +url: string; ``` -The presigned URL for file operations. -Example - `"https://storage.example.com/files/abc123?signature=xyz"` +(`string`) - The presigned URL for file operations. + +- Example - `"https://storage.example.com/files/abc123?signature=xyz"` --- @@ -969,13 +1036,13 @@ Optional custom metadata for each uploaded file. Must match the order of the fil #### Properties -##### processedFiles? +##### processedFiles ```ts -optional processedFiles: FileMetadata[]; +processedFiles: FileMetadata[]; ``` -List of successfully processed files with their metadata. +(`FileMetadata[]`) - List of successfully processed files with their metadata. --- @@ -985,30 +1052,25 @@ Contains version information about the storage service. #### Properties -##### buildVersion? +##### buildVersion ```ts -optional buildVersion: string; +buildVersion: string; ``` -The version number of the storage service build. -Example - `"1.2.3"` +(`string`) - The version number of the storage service build. + +- Example - `"1.2.3"` ## Type Aliases -### GetF +### OutputImageFormat ```ts -type GetF = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; +type OutputImageFormat = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; ``` ---- - -### HeadF - -```ts -type HeadF = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; -``` +Output format for image files. Use 'auto' for content negotiation based on Accept header ## Functions diff --git a/flake.lock b/flake.lock index 24b7cc52..b30c216f 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ ] }, "locked": { - "lastModified": 1749158376, - "narHash": "sha256-uirStFNxauh0lxzBowcp28X+Sq7JgsBIDnbwbAfZwf8=", + "lastModified": 1752002763, + "narHash": "sha256-JYAkdZvpdSx9GUoHPArctYMypSONob4DYKRkOubUWtY=", "owner": "nlewo", "repo": "nix2container", - "rev": "0f8974c58755dba441df03598eefd1e1cd50e341", + "rev": "4f2437f6a1844b843b380d483087ae6d461240ee", "type": "github" }, "original": { @@ -62,27 +62,26 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1751453017, - "narHash": "sha256-s8wL5JtWq1UQHTZy0tl4/05w65xfYTcD2jixUkNVMDg=", + "lastModified": 1754635544, + "narHash": "sha256-/46fUYRUvpwxTb+diHQa8nS/OPJLblKTMOeYXpWsdC0=", "owner": "nhost", "repo": "nixops", - "rev": "fcd4debae991a5b7844be67e3e01d4eb9cc02029", + "rev": "ff80fd734426f1de7599c742dfa3e273c9939049", "type": "github" }, "original": { "owner": "nhost", - "ref": "update-cli", "repo": "nixops", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1745804731, - "narHash": "sha256-v/sK3AS0QKu/Tu5sHIfddiEHCvrbNYPv8X10Fpux68g=", + "lastModified": 1753399495, + "narHash": "sha256-7XG/QBqhrYOyA2houjRTL2NMa7IKZZ/somBqr+Q/6Wo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "29335f23bea5e34228349ea739f31ee79e267b88", + "rev": "0d00f23f023b7215b3f1035adb5247c8ec180dbc", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 657a864f..2330674f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixops.url = "github:nhost/nixops?ref=update-cli"; + nixops.url = "github:nhost/nixops"; nixpkgs.follows = "nixops/nixpkgs"; flake-utils.follows = "nixops/flake-utils"; nix-filter.follows = "nixops/nix-filter"; diff --git a/packages/nhost-js/api/storage.yaml b/packages/nhost-js/api/storage.yaml index 7eac439c..085aff44 100644 --- a/packages/nhost-js/api/storage.yaml +++ b/packages/nhost-js/api/storage.yaml @@ -11,38 +11,25 @@ info: email: "support@nhost.io" url: "https://nhost.io" -paths: - /openapi.yaml: - get: - summary: "Get OpenAPI specification" - description: "Returns the OpenAPI schema definition for this API, allowing clients to understand the available endpoints and models." - operationId: getOpenAPISpec - tags: - - documentation - - excludeme - responses: - "200": - description: "OpenAPI schema definition" - content: - application/x-yaml: - schema: - type: object +servers: + - url: "https://{subdomain}.storage.{region}.nhost.run/v1" + description: "Nhost Storage API Server" - /version: - get: - summary: "Get service version information" - description: "Retrieves build and version information about the storage service. Useful for monitoring and debugging." - operationId: getVersion - tags: - - system - responses: - "200": - description: "Version information successfully retrieved" - content: - application/json: - schema: - $ref: "#/components/schemas/VersionInformation" +tags: + - name: documentation + description: API documentation + - name: excludeme + description: Internal operations excluded from public docs + - name: files + description: File management operations + - name: operations + description: Administrative operations + - name: storage + description: Storage operations and presigned URLs + - name: system + description: System information +paths: /files/: post: summary: "Upload files" @@ -53,6 +40,7 @@ paths: security: - Authorization: [] requestBody: + description: "File upload data including files and optional metadata" required: true content: multipart/form-data: @@ -76,9 +64,6 @@ paths: format: binary required: - file[] - encoding: - file[]: - contentType: application/octet-stream responses: "201": description: "Files successfully uploaded" @@ -92,18 +77,20 @@ paths: description: "List of successfully processed files with their metadata." items: $ref: "#/components/schemas/FileMetadata" - "400": + required: + - processedFiles + default: description: "Error occurred during upload" content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" + $ref: "#/components/schemas/ErrorResponseWithProcessedFiles" /files/{id}: - head: - summary: "Check file information" - description: "Retrieve file metadata headers without downloading the file content. Supports conditional requests and provides caching information." - operationId: getFileMetadataHeaders + delete: + summary: "Delete file" + description: "Permanently delete a file from storage. This removes both the file content and its associated metadata." + operationId: deleteFile tags: - files security: @@ -112,7 +99,32 @@ paths: - name: id required: true in: path - description: "Unique identifier of the file to check" + description: "Unique identifier of the file to delete" + schema: + type: string + responses: + "204": + description: "File successfully deleted" + default: + description: "Error occurred during file deletion" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + get: + summary: "Download file" + description: "Retrieve and download the complete file content. Supports conditional requests, image transformations, and range requests for partial downloads." + operationId: getFile + tags: + - files + security: + - Authorization: [] + parameters: + - name: id + required: true + in: path + description: "Unique identifier of the file to download" schema: type: string - name: if-match @@ -141,20 +153,20 @@ paths: description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query schema: - type: number + type: integer minimum: 1 maximum: 100 - name: h description: "Maximum height to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer minimum: 1 - name: w description: "Maximum width to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer minimum: 1 - name: b description: "Blur the image using this sigma value. Only applies to image files" @@ -165,29 +177,22 @@ paths: - name: f description: "Output format for image files. Use 'auto' for content negotiation based on Accept header" in: query + schema: + $ref: "#/components/schemas/OutputImageFormat" + - name: Range + description: "Range of bytes to retrieve from the file. Format: bytes=start-end" + in: header schema: type: string - default: same - enum: - - auto - - same - - jpeg - - webp - - png - - avif - + pattern: '^bytes=(\d+-\d*|\d*-\d+)(,(\d+-\d*|\d*-\d+))*$' responses: "200": - description: "File information headers retrieved successfully" + description: "File content retrieved successfully" headers: Cache-Control: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" - schema: - type: number Content-Type: description: "MIME type of the file" schema: @@ -196,71 +201,109 @@ paths: description: "Entity tag for cache validation" schema: type: string + Content-Disposition: + description: "Indicates if the content should be displayed inline or as an attachment" + schema: + type: string Last-Modified: description: "Date and time the file was last modified" schema: type: string format: date-time - "304": - description: "File not modified since the condition specified in If-Modified-Since or If-None-Match headers" + Surrogate-Key: + description: "Cache key for surrogate caching" + schema: + type: string + Surrogate-Control: + description: "Cache control directives for surrogate caching" + schema: + type: string + Accept-Ranges: + description: Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges + schema: + type: string + content: + application/octet-stream: {} + "206": + description: "Partial file content retrieved successfully" headers: Cache-Control: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" - schema: - type: number Content-Type: description: "MIME type of the file" schema: type: string + Content-Range: + description: "Range of bytes returned in the response" + schema: + type: string Etag: description: "Entity tag for cache validation" schema: type: string + Content-Disposition: + description: "Indicates if the content should be displayed inline or as an attachment" + schema: + type: string Last-Modified: description: "Date and time the file was last modified" schema: type: string format: date-time - "400": - description: "Error occurred" - headers: - X-Error: - description: "Error message details" + Surrogate-Key: + description: "Cache key for surrogate caching" schema: type: string - "412": - description: "Precondition failed for conditional request headers (If-Match, If-Unmodified-Since)" + Surrogate-Control: + description: "Cache control directives for surrogate caching" + schema: + type: string + content: + application/octet-stream: {} + "304": + description: "File not modified since the condition specified in If-Modified-Since or If-None-Match headers" headers: Cache-Control: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" + Etag: + description: "Entity tag for cache validation" schema: - type: number - Content-Type: - description: "MIME type of the file" + type: string + Surrogate-Control: + description: "Cache control directives for surrogate caching" + schema: + type: string + "412": + description: "Precondition failed for conditional request headers (If-Match, If-Unmodified-Since, If-None-Match)" + headers: + Cache-Control: + description: "Directives for caching mechanisms" schema: type: string Etag: description: "Entity tag for cache validation" schema: type: string - Last-Modified: - description: "Date and time the file was last modified" + Surrogate-Control: + description: "Cache control directives for surrogate caching" + schema: + type: string + default: + description: "Error occurred" + headers: + X-Error: + description: "Error message details" schema: type: string - format: date-time - get: - summary: "Download file" - description: "Retrieve and download the complete file content. Supports conditional requests, image transformations, and range requests for partial downloads." - operationId: getFile + head: + summary: "Check file information" + description: "Retrieve file metadata headers without downloading the file content. Supports conditional requests and provides caching information." + operationId: getFileMetadataHeaders tags: - files security: @@ -269,7 +312,7 @@ paths: - name: id required: true in: path - description: "Unique identifier of the file to download" + description: "Unique identifier of the file to check" schema: type: string - name: if-match @@ -298,20 +341,20 @@ paths: description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query schema: - type: number + type: integer minimum: 1 maximum: 100 - name: h description: "Maximum height to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer minimum: 1 - name: w description: "Maximum width to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer minimum: 1 - name: b description: "Blur the image using this sigma value. Only applies to image files" @@ -323,42 +366,49 @@ paths: description: "Output format for image files. Use 'auto' for content negotiation based on Accept header" in: query schema: - type: string - default: same - enum: - - auto - - same - - jpeg - - webp - - png - - avif + $ref: "#/components/schemas/OutputImageFormat" + responses: "200": - description: "File content retrieved successfully" + description: "File information headers retrieved successfully" headers: Cache-Control: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" - schema: - type: number Content-Type: description: "MIME type of the file" schema: type: string + Content-Length: + description: "Size of the file in bytes" + schema: + type: integer Etag: description: "Entity tag for cache validation" schema: type: string + Content-Disposition: + description: "Indicates if the content should be displayed inline or as an attachment" + schema: + type: string Last-Modified: description: "Date and time the file was last modified" schema: type: string format: date-time - content: - application/octet-stream: {} + Accept-Ranges: + description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges" + schema: + type: string + Surrogate-Key: + description: "Cache key for surrogate caching" + schema: + type: string + Surrogate-Control: + description: "Cache control directives for surrogate caching" + schema: + type: string "304": description: "File not modified since the condition specified in If-Modified-Since or If-None-Match headers" headers: @@ -366,48 +416,30 @@ paths: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" - schema: - type: number - Content-Type: - description: "MIME type of the file" - schema: - type: string Etag: description: "Entity tag for cache validation" schema: type: string - Last-Modified: - description: "Date and time the file was last modified" + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string - format: date-time "412": - description: "Precondition failed for conditional request headers (If-Match, If-Unmodified-Since, If-None-Match)" + description: "Precondition failed for conditional request headers (If-Match, If-Unmodified-Since)" headers: Cache-Control: description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: "Size of the file in bytes" - schema: - type: number - Content-Type: - description: "MIME type of the file" - schema: - type: string Etag: description: "Entity tag for cache validation" schema: type: string - Last-Modified: - description: "Date and time the file was last modified" + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string - format: date-time - "400": + default: description: "Error occurred" headers: X-Error: @@ -437,6 +469,7 @@ paths: schema: type: string requestBody: + description: "File replacement data including new file content and optional metadata" required: true content: multipart/form-data: @@ -445,14 +478,10 @@ paths: properties: metadata: $ref: "#/components/schemas/UpdateFileMetadata" - description: "Optional metadata to update for the file" file: description: "New file content to replace the existing file" type: string format: binary - encoding: - file: - contentType: application/octet-stream responses: "200": description: "File successfully replaced" @@ -460,42 +489,17 @@ paths: application/json: schema: $ref: "#/components/schemas/FileMetadata" - "400": + default: description: "Error occurred during file replacement" content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - delete: - summary: "Delete file" - description: "Permanently delete a file from storage. This removes both the file content and its associated metadata." - operationId: deleteFile - tags: - - files - security: - - Authorization: [] - parameters: - - name: id - required: true - in: path - description: "Unique identifier of the file to delete" - schema: - type: string - responses: - "204": - description: "File successfully deleted" - "400": - description: "Error occurred during file deletion" - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - /files/{id}/presignedurl: get: summary: Retrieve presigned URL to retrieve the file - operationId: getPresignedURL + operationId: getFilePresignedURL description: | Retrieve presigned URL to retrieve the file. Expiration of the URL is determined by bucket configuration @@ -507,6 +511,7 @@ paths: - name: id required: true in: path + description: "Unique identifier of the file" schema: type: string responses: @@ -517,7 +522,7 @@ paths: schema: $ref: "#/components/schemas/PresignedURLResponse" - "400": + default: description: Some error occurred content: application/json: @@ -527,7 +532,7 @@ paths: /files/{id}/presignedurl/contents: get: summary: Retrieve contents of file - operationId: getPresignedURLContents + operationId: getFileWithPresignedURL description: Retrieve contents of file tags: - storage @@ -538,6 +543,7 @@ paths: - name: id required: true in: path + description: "Unique identifier of the file" schema: type: string - name: X-Amz-Algorithm @@ -576,68 +582,107 @@ paths: in: query schema: type: string + - name: X-Amz-Checksum-Mode + description: Use presignedurl endpoint to generate this automatically + required: true + in: query + schema: + type: string + - name: x-id + description: Use presignedurl endpoint to generate this automatically + required: true + in: query + schema: + type: string - name: if-match - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match + description: "Only return the file if the current ETag matches one of the values provided" in: header schema: type: string - name: if-none-match - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match + description: "Only return the file if the current ETag does not match any of the values provided" in: header schema: type: string - name: if-modified-since - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since + description: "Only return the file if it has been modified after the given date" in: header schema: type: string + format: date-time - name: if-unmodified-since - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since + description: "Only return the file if it has not been modified after the given date" in: header schema: type: string + format: date-time - name: q - description: Quality of the image. Only applies to jpeg, webp and png files + description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query schema: - type: number + type: integer + minimum: 1 + maximum: 100 - name: h - description: Resize image up to h maintaining aspect ratio. Only applies to jpeg, webp and png files + description: "Maximum height to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer + minimum: 1 - name: w - description: Resize image up to w maintaining aspect ratio. Only applies to jpeg, webp and png files + description: "Maximum width to resize image to while maintaining aspect ratio. Only applies to image files" in: query schema: - type: number + type: integer + minimum: 1 - name: b - description: Blur the image according to this sigma value. Only applies to jpeg, webp and png files + description: "Blur the image using this sigma value. Only applies to image files" in: query schema: type: number + minimum: 0 + - name: f + description: "Output format for image files. Use 'auto' for content negotiation based on Accept header" + in: query + schema: + $ref: "#/components/schemas/OutputImageFormat" + - name: Range + description: "Range of bytes to retrieve from the file. Format: bytes=start-end" + in: header + schema: + type: string + pattern: '^bytes=(\d+-\d*|\d*-\d+)(,(\d+-\d*|\d*-\d+))*$' responses: "200": - description: File gathered successfully + description: "File content retrieved successfully" headers: Cache-Control: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length - schema: - type: number Content-Type: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + description: "MIME type of the file" schema: type: string Etag: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Etag + description: "Entity tag for cache validation" + schema: + type: string + Content-Disposition: + description: "Indicates if the content should be displayed inline or as an attachment" schema: type: string Last-Modified: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified + description: "Date and time the file was last modified" + schema: + type: string + format: date-time + Surrogate-Key: + description: "Cache key for surrogate caching" + schema: + type: string + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string Accept-Ranges: @@ -647,120 +692,125 @@ paths: content: application/octet-stream: {} "206": - description: File partially gathered successfully + description: "Partial file content retrieved successfully" headers: Cache-Control: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + description: "Directives for caching mechanisms" schema: type: string - Content-Length: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length - schema: - type: number Content-Type: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + description: "MIME type of the file" + schema: + type: string + Content-Range: + description: "Range of bytes returned in the response" schema: type: string Etag: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Etag + description: "Entity tag for cache validation" + schema: + type: string + Content-Disposition: + description: "Indicates if the content should be displayed inline or as an attachment" schema: type: string Last-Modified: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified + description: "Date and time the file was last modified" schema: type: string - Accept-Ranges: - description: Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges + format: date-time + Surrogate-Key: + description: "Cache key for surrogate caching" schema: type: string - Content-Range: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string content: application/octet-stream: {} "304": - description: | - File hasn't been modified based on: - - file modification time is older than If-Modified-Since + description: "File not modified since the condition specified in If-Modified-Since or If-None-Match headers" headers: Cache-Control: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - schema: - type: string - Content-Length: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length - schema: - type: number - Content-Type: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + description: "Directives for caching mechanisms" schema: type: string Etag: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Etag + description: "Entity tag for cache validation" schema: type: string - Last-Modified: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string "412": - description: | - Some of the conditions specified in the headers failed to match. For instance: - - etag doesn't match one of If-Match - - etag matches one of If-None-Match - - if-unmodified-since is false + description: "Precondition failed for conditional request headers (If-Match, If-Unmodified-Since, If-None-Match)" headers: Cache-Control: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - schema: - type: string - Content-Length: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length - schema: - type: number - Content-Type: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + description: "Directives for caching mechanisms" schema: type: string Etag: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Etag + description: "Entity tag for cache validation" schema: type: string - Last-Modified: - description: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified + Surrogate-Control: + description: "Cache control directives for surrogate caching" schema: type: string - "400": - description: Some error occurred + default: + description: "Error occurred" headers: X-Error: - description: An error message + description: "Error message details" schema: type: string - /ops/list-orphans: + /openapi.yaml: + get: + summary: "Get OpenAPI specification" + description: "Returns the OpenAPI schema definition for this API, allowing clients to understand the available endpoints and models." + operationId: getOpenAPISpec + tags: + - documentation + - excludeme + responses: + "200": + description: "OpenAPI schema definition" + content: + application/x-yaml: + schema: + type: object + default: + description: "Error occurred" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /ops/delete-broken-metadata: post: - summary: Lists orphaned files - operationId: listOrphanedFiles - description: Orphaned files are files that are present in the storage but have no associated metadata. This is an admin operation that requires the Hasura admin secret. + summary: Delete broken metadata + operationId: deleteBrokenMetadata + description: Broken metadata is defined as metadata that has isUploaded = true but there is no file in the storage matching it. This is an admin operation that requires the Hasura admin secret. tags: - operations security: - X-Hasura-Admin-Secret: [] responses: "200": - description: Successfully computed orphaned files + description: Successfully deleted broken metadata content: application/json: schema: type: object properties: - files: + metadata: type: array items: - type: string - "400": + $ref: "#/components/schemas/FileSummary" + default: description: En error occured content: application/json: @@ -788,7 +838,7 @@ paths: type: array items: type: string - "400": + default: description: En error occured content: application/json: @@ -816,25 +866,25 @@ paths: type: array items: $ref: "#/components/schemas/FileSummary" - "400": + default: description: En error occured content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - /ops/delete-broken-metadata: + /ops/list-not-uploaded: post: - summary: Delete broken metadata - operationId: deleteBrokenMetadata - description: Broken metadata is defined as metadata that has isUploaded = true but there is no file in the storage matching it. This is an admin operation that requires the Hasura admin secret. + summary: Lists files that haven't been uploaded + operationId: listFilesNotUploaded + description: That is, metadata that has isUploaded = false. This is an admin operation that requires the Hasura admin secret. tags: - operations security: - X-Hasura-Admin-Secret: [] responses: "200": - description: Successfully deleted broken metadata + description: Successfully checked files not uploaded content: application/json: schema: @@ -844,48 +894,69 @@ paths: type: array items: $ref: "#/components/schemas/FileSummary" - "400": + default: description: En error occured content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - /ops/list-not-uploaded: + /ops/list-orphans: post: - summary: Lists files that haven't been uploaded - operationId: listFilesNotUploaded - description: That is, metadata that has isUploaded = false. This is an admin operation that requires the Hasura admin secret. + summary: Lists orphaned files + operationId: listOrphanedFiles + description: Orphaned files are files that are present in the storage but have no associated metadata. This is an admin operation that requires the Hasura admin secret. tags: - operations security: - X-Hasura-Admin-Secret: [] responses: "200": - description: Successfully checked files not uploaded + description: Successfully computed orphaned files content: application/json: schema: type: object properties: - metadata: + files: type: array items: - $ref: "#/components/schemas/FileSummary" - "400": + type: string + default: description: En error occured content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /version: + get: + summary: "Get service version information" + description: "Retrieves build and version information about the storage service. Useful for monitoring and debugging." + operationId: getVersion + tags: + - system + responses: + "200": + description: "Version information successfully retrieved" + content: + application/json: + schema: + $ref: "#/components/schemas/VersionInformation" + default: + description: "Error occurred" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + components: securitySchemes: Authorization: type: http scheme: bearer bearerFormat: JWT - description: "Bearer authentication token for authorizing client requests. Generated via Nhost Auth service." + description: API key to authorize requests. X-Hasura-Admin-Secret: type: apiKey name: X-Hasura-Admin-Secret @@ -893,36 +964,51 @@ components: description: "Hasura admin secret key for backend/administrative operations." schemas: - VersionInformation: + ErrorResponse: type: object - description: "Contains version information about the storage service." + description: "Error information returned by the API." properties: - buildVersion: - type: string - description: "The version number of the storage service build." - example: "1.2.3" + error: + type: object + additionalProperties: false + description: "Error details." + properties: + message: + type: string + description: "Human-readable error message." + example: "File not found" + data: + type: object + additionalProperties: true + description: "Additional data related to the error, if any." + required: + - message additionalProperties: false - FileSummary: + ErrorResponseWithProcessedFiles: type: object - description: "Basic information about a file in storage." + description: "Error information returned by the API." properties: - id: - type: string - description: "Unique identifier for the file." - example: "d5e76ceb-77a2-4153-b7da-1f7c115b2ff2" - name: - type: string - description: "Name of the file including extension." - example: "profile-picture.jpg" - bucketId: - type: string - description: "ID of the bucket containing the file." - example: "users-bucket" - isUploaded: - type: boolean - description: "Whether the file has been successfully uploaded." - example: true + processedFiles: + type: array + description: "List of files that were successfully processed before the error occurred." + items: + $ref: "#/components/schemas/FileMetadata" + error: + type: object + additionalProperties: false + description: "Error details." + properties: + message: + type: string + description: "Human-readable error message." + example: "File not found" + data: + type: object + additionalProperties: true + description: "Additional data related to the error, if any." + required: + - message additionalProperties: false FileMetadata: @@ -938,8 +1024,9 @@ components: description: "Name of the file including extension." example: "profile-picture.jpg" size: - type: number description: "Size of the file in bytes." + type: integer + format: int64 example: 245678 bucketId: type: string @@ -976,25 +1063,60 @@ components: additionalProperties: true description: "Custom metadata associated with the file." example: { "alt": "Profile picture", "category": "avatar" } + required: + - id + - name + - size + - bucketId + - etag + - createdAt + - updatedAt + - isUploaded + - mimeType additionalProperties: false - UploadFileMetadata: + FileSummary: type: object - description: "Metadata provided when uploading a new file." + description: "Basic information about a file in storage." properties: id: type: string - description: "Optional custom ID for the file. If not provided, a UUID will be generated." - example: "custom-id-123" + description: "Unique identifier for the file." + example: "d5e76ceb-77a2-4153-b7da-1f7c115b2ff2" name: type: string - description: "Name to assign to the file. If not provided, the original filename will be used." - example: "custom-filename.png" - metadata: - type: object - additionalProperties: true - description: "Custom metadata to associate with the file." - example: { "alt": "Custom image", "category": "document" } + description: "Name of the file including extension." + example: "profile-picture.jpg" + bucketId: + type: string + description: "ID of the bucket containing the file." + example: "users-bucket" + isUploaded: + type: boolean + description: "Whether the file has been successfully uploaded." + example: true + additionalProperties: false + required: + - id + - name + - bucketId + - isUploaded + + PresignedURLResponse: + type: object + description: "Contains a presigned URL for direct file operations." + properties: + url: + type: string + description: "The presigned URL for file operations." + example: "https://storage.example.com/files/abc123?signature=xyz" + expiration: + type: integer + description: "The time in seconds until the URL expires." + example: 3600 + required: + - url + - expiration additionalProperties: false UpdateFileMetadata: @@ -1012,33 +1134,46 @@ components: example: { "alt": "Updated image description", "category": "profile" } additionalProperties: false - PresignedURLResponse: + UploadFileMetadata: type: object - description: "Contains a presigned URL for direct file operations." + description: "Metadata provided when uploading a new file." properties: - url: + id: type: string - description: "The presigned URL for file operations." - example: "https://storage.example.com/files/abc123?signature=xyz" - expiration: - type: number - description: "The time in seconds until the URL expires." - example: 3600 + description: "Optional custom ID for the file. If not provided, a UUID will be generated." + example: "custom-id-123" + name: + type: string + description: "Name to assign to the file. If not provided, the original filename will be used." + example: "custom-filename.png" + metadata: + type: object + additionalProperties: true + description: "Custom metadata to associate with the file." + example: { "alt": "Custom image", "category": "document" } additionalProperties: false - ErrorResponse: + VersionInformation: type: object - description: "Error information returned by the API." - properties: - error: - type: object - additionalProperties: false - description: "Error details." - properties: - message: - type: string - description: "Human-readable error message." - example: "File not found" - required: - - message + description: "Contains version information about the storage service." additionalProperties: false + properties: + buildVersion: + type: string + description: "The version number of the storage service build." + example: "1.2.3" + required: + - buildVersion + + OutputImageFormat: + type: string + description: "Output format for image files. Use 'auto' for content negotiation based on Accept header" + default: same + enum: + - auto + - same + - jpeg + - webp + - png + - avif + example: same diff --git a/packages/nhost-js/src/__tests__/storage.test.ts b/packages/nhost-js/src/__tests__/storage.test.ts index c5fcf91e..de6e41e0 100644 --- a/packages/nhost-js/src/__tests__/storage.test.ts +++ b/packages/nhost-js/src/__tests__/storage.test.ts @@ -1,4 +1,4 @@ -import { describe, beforeAll, it, expect } from "@jest/globals"; +import { describe, it, expect, beforeAll } from "@jest/globals"; import { createClient } from "@nhost/nhost-js"; import { type ErrorResponse } from "@nhost/nhost-js/storage"; import { type FetchError } from "@nhost/nhost-js/fetch"; @@ -36,7 +36,7 @@ describe("Test Storage API", () => { expect(resp.status).toBe(200); expect(resp.body).toBeDefined(); - expect(resp.body.buildVersion).toBe("0.7.2"); + expect(resp.body.buildVersion).toBe("0.8.0-beta1"); }); it("should upload a file", async () => { @@ -114,10 +114,9 @@ describe("Test Storage API", () => { expect(err).toBeDefined(); expect(err.status).toBe(400); expect(err.body).toBeDefined(); - expect(err.body.error?.message).toBe( - "file[] not found in Multipart form", + expect(err.body.error).toBe( + 'error in openapi3filter.RequestError: request body has an error: doesn\'t match schema: Error at "/file[]": property "file[]" is missing', ); - expect(err.headers.get("content-length")).toBe("58"); expect(err.headers.get("content-type")).toBe( "application/json; charset=utf-8", ); @@ -136,7 +135,7 @@ describe("Test Storage API", () => { expect(resp.headers.get("last-modified")).toBeDefined(); expect(resp.headers.get("surrogate-key")).toBeDefined(); expect(resp.headers.get("cache-control")).toBe("max-age=3600"); - expect(resp.headers.get("surrogate-control")).toBe("max-age=604800"); + expect(resp.headers.get("surrogate-control")).toBe("max-age=3600"); expect(resp.headers.get("content-length")).toBe("5"); expect(resp.headers.get("date")).toBeDefined(); @@ -162,7 +161,7 @@ describe("Test Storage API", () => { expect(err.headers).toBeDefined(); expect(err.headers.get("etag")).toBe(etag); expect(err.headers.get("cache-control")).toBe("max-age=3600"); - expect(err.headers.get("surrogate-control")).toBe("max-age=604800"); + expect(err.headers.get("surrogate-control")).toBe("max-age=3600"); expect(err.headers.get("date")).toBeDefined(); return; } @@ -185,7 +184,7 @@ describe("Test Storage API", () => { expect(resp.headers.get("last-modified")).toBeDefined(); expect(resp.headers.get("surrogate-key")).toBeDefined(); expect(resp.headers.get("cache-control")).toBe("max-age=3600"); - expect(resp.headers.get("surrogate-control")).toBe("max-age=604800"); + expect(resp.headers.get("surrogate-control")).toBe("max-age=3600"); expect(resp.headers.get("content-length")).toBe("5"); expect(resp.headers.get("date")).toBeDefined(); }); @@ -200,7 +199,7 @@ describe("Test Storage API", () => { expect(resp.headers.get("last-modified")).toBeDefined(); expect(resp.headers.get("surrogate-key")).toBeDefined(); expect(resp.headers.get("cache-control")).toBe("max-age=3600"); - expect(resp.headers.get("surrogate-control")).toBe("max-age=604800"); + expect(resp.headers.get("surrogate-control")).toBe("max-age=3600"); expect(resp.headers.get("content-length")).toBe("5"); expect(resp.headers.get("date")).toBeDefined(); expect(await resp.body.text()).toBe("test1"); @@ -225,7 +224,7 @@ describe("Test Storage API", () => { expect(err.headers).toBeDefined(); expect(err.headers.get("etag")).toBe(etag); expect(err.headers.get("cache-control")).toBe("max-age=3600"); - expect(err.headers.get("surrogate-control")).toBe("max-age=604800"); + expect(err.headers.get("surrogate-control")).toBe("max-age=3600"); expect(err.headers.get("date")).toBeDefined(); } }); diff --git a/packages/nhost-js/src/storage/__tests__/docstrings.test.ts b/packages/nhost-js/src/storage/__tests__/docstrings.test.ts index 4474c6b4..7b857e8f 100644 --- a/packages/nhost-js/src/storage/__tests__/docstrings.test.ts +++ b/packages/nhost-js/src/storage/__tests__/docstrings.test.ts @@ -96,8 +96,10 @@ test("error handling for storage", async () => { expect(err.status).toBe(403); expect(err.body).toStrictEqual({ error: { + data: null, message: "you are not authorized", }, + processedFiles: [], }); } }); diff --git a/packages/nhost-js/src/storage/client.ts b/packages/nhost-js/src/storage/client.ts index 4298779c..efcb0003 100644 --- a/packages/nhost-js/src/storage/client.ts +++ b/packages/nhost-js/src/storage/client.ts @@ -6,71 +6,86 @@ import { FetchError, createEnhancedFetch } from "../fetch"; import type { ChainFunction, FetchResponse } from "../fetch"; /** - * Contains version information about the storage service. - @property buildVersion? (`string`) - The version number of the storage service build. - * Example - `"1.2.3"`*/ -export interface VersionInformation { + * Error details. + @property message (`string`) - Human-readable error message. + * Example - `"File not found"` + @property data? (`Record`) - Additional data related to the error, if any.*/ +export interface ErrorResponseError { /** - * The version number of the storage service build. - * Example - `"1.2.3"` + * Human-readable error message. + * Example - `"File not found"` */ - buildVersion?: string; + message: string; + /** + * Additional data related to the error, if any. + */ + data?: Record; } /** - * Basic information about a file in storage. - @property id? (`string`) - Unique identifier for the file. - * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` - @property name? (`string`) - Name of the file including extension. - * Example - `"profile-picture.jpg"` - @property bucketId? (`string`) - ID of the bucket containing the file. - * Example - `"users-bucket"` - @property isUploaded? (`boolean`) - Whether the file has been successfully uploaded. - * Example - `true`*/ -export interface FileSummary { + * Error information returned by the API. + @property error? (`ErrorResponseError`) - Error details.*/ +export interface ErrorResponse { /** - * Unique identifier for the file. - * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` + * Error details. */ - id?: string; + error?: ErrorResponseError; +} + +/** + * Error details. + @property message (`string`) - Human-readable error message. + * Example - `"File not found"` + @property data? (`Record`) - Additional data related to the error, if any.*/ +export interface ErrorResponseWithProcessedFilesError { /** - * Name of the file including extension. - * Example - `"profile-picture.jpg"` + * Human-readable error message. + * Example - `"File not found"` */ - name?: string; + message: string; /** - * ID of the bucket containing the file. - * Example - `"users-bucket"` + * Additional data related to the error, if any. */ - bucketId?: string; + data?: Record; +} + +/** + * Error information returned by the API. + @property processedFiles? (`FileMetadata[]`) - List of files that were successfully processed before the error occurred. + @property error? (`ErrorResponseWithProcessedFilesError`) - Error details.*/ +export interface ErrorResponseWithProcessedFiles { /** - * Whether the file has been successfully uploaded. - * Example - `true` + * List of files that were successfully processed before the error occurred. */ - isUploaded?: boolean; + processedFiles?: FileMetadata[]; + /** + * Error details. + */ + error?: ErrorResponseWithProcessedFilesError; } /** * Comprehensive metadata information about a file in storage. - @property id? (`string`) - Unique identifier for the file. + @property id (`string`) - Unique identifier for the file. * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` - @property name? (`string`) - Name of the file including extension. + @property name (`string`) - Name of the file including extension. * Example - `"profile-picture.jpg"` - @property size? (`number`) - Size of the file in bytes. + @property size (`number`) - Size of the file in bytes. * Example - `245678` - @property bucketId? (`string`) - ID of the bucket containing the file. + * Format - int64 + @property bucketId (`string`) - ID of the bucket containing the file. * Example - `"users-bucket"` - @property etag? (`string`) - Entity tag for cache validation. + @property etag (`string`) - Entity tag for cache validation. * Example - `"\"a1b2c3d4e5f6\""` - @property createdAt? (`string`) - Timestamp when the file was created. + @property createdAt (`string`) - Timestamp when the file was created. * Example - `"2023-01-15T12:34:56Z"` * Format - date-time - @property updatedAt? (`string`) - Timestamp when the file was last updated. + @property updatedAt (`string`) - Timestamp when the file was last updated. * Example - `"2023-01-16T09:45:32Z"` * Format - date-time - @property isUploaded? (`boolean`) - Whether the file has been successfully uploaded. + @property isUploaded (`boolean`) - Whether the file has been successfully uploaded. * Example - `true` - @property mimeType? (`string`) - MIME type of the file. + @property mimeType (`string`) - MIME type of the file. * Example - `"image/jpeg"` @property uploadedByUserId? (`string`) - ID of the user who uploaded the file. * Example - `"abc123def456"` @@ -81,49 +96,50 @@ export interface FileMetadata { * Unique identifier for the file. * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` */ - id?: string; + id: string; /** * Name of the file including extension. * Example - `"profile-picture.jpg"` */ - name?: string; + name: string; /** * Size of the file in bytes. * Example - `245678` + * Format - int64 */ - size?: number; + size: number; /** * ID of the bucket containing the file. * Example - `"users-bucket"` */ - bucketId?: string; + bucketId: string; /** * Entity tag for cache validation. * Example - `"\"a1b2c3d4e5f6\""` */ - etag?: string; + etag: string; /** * Timestamp when the file was created. * Example - `"2023-01-15T12:34:56Z"` * Format - date-time */ - createdAt?: string; + createdAt: string; /** * Timestamp when the file was last updated. * Example - `"2023-01-16T09:45:32Z"` * Format - date-time */ - updatedAt?: string; + updatedAt: string; /** * Whether the file has been successfully uploaded. * Example - `true` */ - isUploaded?: boolean; + isUploaded: boolean; /** * MIME type of the file. * Example - `"image/jpeg"` */ - mimeType?: string; + mimeType: string; /** * ID of the user who uploaded the file. * Example - `"abc123def456"` @@ -137,29 +153,55 @@ export interface FileMetadata { } /** - * Metadata provided when uploading a new file. - @property id? (`string`) - Optional custom ID for the file. If not provided, a UUID will be generated. - * Example - `"custom-id-123"` - @property name? (`string`) - Name to assign to the file. If not provided, the original filename will be used. - * Example - `"custom-filename.png"` - @property metadata? (`Record`) - Custom metadata to associate with the file. - * Example - `{"alt":"Custom image","category":"document"}`*/ -export interface UploadFileMetadata { + * Basic information about a file in storage. + @property id (`string`) - Unique identifier for the file. + * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` + @property name (`string`) - Name of the file including extension. + * Example - `"profile-picture.jpg"` + @property bucketId (`string`) - ID of the bucket containing the file. + * Example - `"users-bucket"` + @property isUploaded (`boolean`) - Whether the file has been successfully uploaded. + * Example - `true`*/ +export interface FileSummary { /** - * Optional custom ID for the file. If not provided, a UUID will be generated. - * Example - `"custom-id-123"` + * Unique identifier for the file. + * Example - `"d5e76ceb-77a2-4153-b7da-1f7c115b2ff2"` */ - id?: string; + id: string; /** - * Name to assign to the file. If not provided, the original filename will be used. - * Example - `"custom-filename.png"` + * Name of the file including extension. + * Example - `"profile-picture.jpg"` */ - name?: string; + name: string; /** - * Custom metadata to associate with the file. - * Example - `{"alt":"Custom image","category":"document"}` + * ID of the bucket containing the file. + * Example - `"users-bucket"` */ - metadata?: Record; + bucketId: string; + /** + * Whether the file has been successfully uploaded. + * Example - `true` + */ + isUploaded: boolean; +} + +/** + * Contains a presigned URL for direct file operations. + @property url (`string`) - The presigned URL for file operations. + * Example - `"https://storage.example.com/files/abc123?signature=xyz"` + @property expiration (`number`) - The time in seconds until the URL expires. + * Example - `3600`*/ +export interface PresignedURLResponse { + /** + * The presigned URL for file operations. + * Example - `"https://storage.example.com/files/abc123?signature=xyz"` + */ + url: string; + /** + * The time in seconds until the URL expires. + * Example - `3600` + */ + expiration: number; } /** @@ -182,46 +224,54 @@ export interface UpdateFileMetadata { } /** - * Contains a presigned URL for direct file operations. - @property url? (`string`) - The presigned URL for file operations. - * Example - `"https://storage.example.com/files/abc123?signature=xyz"` - @property expiration? (`number`) - The time in seconds until the URL expires. - * Example - `3600`*/ -export interface PresignedURLResponse { + * Metadata provided when uploading a new file. + @property id? (`string`) - Optional custom ID for the file. If not provided, a UUID will be generated. + * Example - `"custom-id-123"` + @property name? (`string`) - Name to assign to the file. If not provided, the original filename will be used. + * Example - `"custom-filename.png"` + @property metadata? (`Record`) - Custom metadata to associate with the file. + * Example - `{"alt":"Custom image","category":"document"}`*/ +export interface UploadFileMetadata { /** - * The presigned URL for file operations. - * Example - `"https://storage.example.com/files/abc123?signature=xyz"` + * Optional custom ID for the file. If not provided, a UUID will be generated. + * Example - `"custom-id-123"` */ - url?: string; + id?: string; /** - * The time in seconds until the URL expires. - * Example - `3600` + * Name to assign to the file. If not provided, the original filename will be used. + * Example - `"custom-filename.png"` */ - expiration?: number; -} - -/** - * Error details. - @property message (`string`) - Human-readable error message. - * Example - `"File not found"`*/ -export interface ErrorResponseError { + name?: string; /** - * Human-readable error message. - * Example - `"File not found"` + * Custom metadata to associate with the file. + * Example - `{"alt":"Custom image","category":"document"}` */ - message: string; + metadata?: Record; } /** - * Error information returned by the API. - @property error? (`ErrorResponseError`) - Error details.*/ -export interface ErrorResponse { + * Contains version information about the storage service. + @property buildVersion (`string`) - The version number of the storage service build. + * Example - `"1.2.3"`*/ +export interface VersionInformation { /** - * Error details. + * The version number of the storage service build. + * Example - `"1.2.3"` */ - error?: ErrorResponseError; + buildVersion: string; } +/** + * Output format for image files. Use 'auto' for content negotiation based on Accept header + */ +export type OutputImageFormat = + | "auto" + | "same" + | "jpeg" + | "webp" + | "png" + | "avif"; + /** * @property bucket-id? (`string`) - Target bucket identifier where files will be stored. @@ -246,24 +296,14 @@ export interface UploadFilesBody { /** * - @property processedFiles? (`FileMetadata[]`) - List of successfully processed files with their metadata.*/ + @property processedFiles (`FileMetadata[]`) - List of successfully processed files with their metadata.*/ export interface UploadFilesResponse201 { /** * List of successfully processed files with their metadata. */ - processedFiles?: FileMetadata[]; + processedFiles: FileMetadata[]; } -/** - * - */ -export type HeadF = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; - -/** - * - */ -export type GetF = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; - /** * @property metadata? (`UpdateFileMetadata`) - Metadata that can be updated for an existing file. @@ -283,12 +323,12 @@ export interface ReplaceFileBody { /** * - @property files? (`string[]`) - */ -export interface ListOrphanedFilesResponse200 { + @property metadata? (`FileSummary[]`) - */ +export interface DeleteBrokenMetadataResponse200 { /** * */ - files?: string[]; + metadata?: FileSummary[]; } /** @@ -314,7 +354,7 @@ export interface ListBrokenMetadataResponse200 { /** * @property metadata? (`FileSummary[]`) - */ -export interface DeleteBrokenMetadataResponse200 { +export interface ListFilesNotUploadedResponse200 { /** * */ @@ -323,16 +363,16 @@ export interface DeleteBrokenMetadataResponse200 { /** * - @property metadata? (`FileSummary[]`) - */ -export interface ListFilesNotUploadedResponse200 { + @property files? (`string[]`) - */ +export interface ListOrphanedFilesResponse200 { /** * */ - metadata?: FileSummary[]; + files?: string[]; } /** - * Parameters for the getFileMetadataHeaders method. + * Parameters for the getFile method. @property q? (number) - Image quality (1-100). Only applies to JPEG, WebP and PNG files @property h? (number) - Maximum height to resize image to while maintaining aspect ratio. Only applies to image files @@ -341,9 +381,10 @@ export interface ListFilesNotUploadedResponse200 { @property b? (number) - Blur the image using this sigma value. Only applies to image files - @property f? (HeadF) - Output format for image files. Use 'auto' for content negotiation based on Accept header - */ -export interface GetFileMetadataHeadersParams { + @property f? (OutputImageFormat) - Output format for image files. Use 'auto' for content negotiation based on Accept header + + * Output format for image files. Use 'auto' for content negotiation based on Accept header*/ +export interface GetFileParams { /** * Image quality (1-100). Only applies to JPEG, WebP and PNG files @@ -367,11 +408,12 @@ export interface GetFileMetadataHeadersParams { /** * Output format for image files. Use 'auto' for content negotiation based on Accept header + * Output format for image files. Use 'auto' for content negotiation based on Accept header */ - f?: HeadF; + f?: OutputImageFormat; } /** - * Parameters for the getFile method. + * Parameters for the getFileMetadataHeaders method. @property q? (number) - Image quality (1-100). Only applies to JPEG, WebP and PNG files @property h? (number) - Maximum height to resize image to while maintaining aspect ratio. Only applies to image files @@ -380,9 +422,10 @@ export interface GetFileMetadataHeadersParams { @property b? (number) - Blur the image using this sigma value. Only applies to image files - @property f? (GetF) - Output format for image files. Use 'auto' for content negotiation based on Accept header - */ -export interface GetFileParams { + @property f? (OutputImageFormat) - Output format for image files. Use 'auto' for content negotiation based on Accept header + + * Output format for image files. Use 'auto' for content negotiation based on Accept header*/ +export interface GetFileMetadataHeadersParams { /** * Image quality (1-100). Only applies to JPEG, WebP and PNG files @@ -406,29 +449,20 @@ export interface GetFileParams { /** * Output format for image files. Use 'auto' for content negotiation based on Accept header + * Output format for image files. Use 'auto' for content negotiation based on Accept header */ - f?: GetF; + f?: OutputImageFormat; } export interface Client { baseURL: string; pushChainFunction(chainFunction: ChainFunction): void; - /** - Summary: Get service version information - Retrieves build and version information about the storage service. Useful for monitoring and debugging. - - This method may return different T based on the response code: - - 200: VersionInformation - */ - getVersion(options?: RequestInit): Promise>; - /** Summary: Upload files Upload one or more files to a specified bucket. Supports batch uploading with optional custom metadata for each file. If uploading multiple files, either provide metadata for all files or none. This method may return different T based on the response code: - 201: UploadFilesResponse201 - - 400: ErrorResponse */ uploadFiles( body: UploadFilesBody, @@ -436,20 +470,13 @@ export interface Client { ): Promise>; /** - Summary: Check file information - Retrieve file metadata headers without downloading the file content. Supports conditional requests and provides caching information. + Summary: Delete file + Permanently delete a file from storage. This removes both the file content and its associated metadata. This method may return different T based on the response code: - - 200: void - - 304: void - - 400: void - - 412: void + - 204: void */ - getFileMetadataHeaders( - id: string, - params?: GetFileMetadataHeadersParams, - options?: RequestInit, - ): Promise>; + deleteFile(id: string, options?: RequestInit): Promise>; /** Summary: Download file @@ -457,8 +484,8 @@ export interface Client { This method may return different T based on the response code: - 200: void + - 206: void - 304: void - - 400: void - 412: void */ getFile( @@ -467,6 +494,21 @@ export interface Client { options?: RequestInit, ): Promise>; + /** + Summary: Check file information + Retrieve file metadata headers without downloading the file content. Supports conditional requests and provides caching information. + + This method may return different T based on the response code: + - 200: void + - 304: void + - 412: void + */ + getFileMetadataHeaders( + id: string, + params?: GetFileMetadataHeadersParams, + options?: RequestInit, + ): Promise>; + /** Summary: Replace file Replace an existing file with new content while preserving the file ID. The operation follows these steps: @@ -479,7 +521,6 @@ Each step is atomic, but if a step fails, previous steps will not be automatical This method may return different T based on the response code: - 200: FileMetadata - - 400: ErrorResponse */ replaceFile( id: string, @@ -487,16 +528,6 @@ Each step is atomic, but if a step fails, previous steps will not be automatical options?: RequestInit, ): Promise>; - /** - Summary: Delete file - Permanently delete a file from storage. This removes both the file content and its associated metadata. - - This method may return different T based on the response code: - - 204: void - - 400: ErrorResponse - */ - deleteFile(id: string, options?: RequestInit): Promise>; - /** Summary: Retrieve presigned URL to retrieve the file Retrieve presigned URL to retrieve the file. Expiration of the URL is @@ -505,24 +536,22 @@ determined by bucket configuration This method may return different T based on the response code: - 200: PresignedURLResponse - - 400: ErrorResponse */ - getPresignedURL( + getFilePresignedURL( id: string, options?: RequestInit, ): Promise>; /** - Summary: Lists orphaned files - Orphaned files are files that are present in the storage but have no associated metadata. This is an admin operation that requires the Hasura admin secret. + Summary: Delete broken metadata + Broken metadata is defined as metadata that has isUploaded = true but there is no file in the storage matching it. This is an admin operation that requires the Hasura admin secret. This method may return different T based on the response code: - - 200: ListOrphanedFilesResponse200 - - 400: ErrorResponse + - 200: DeleteBrokenMetadataResponse200 */ - listOrphanedFiles( + deleteBrokenMetadata( options?: RequestInit, - ): Promise>; + ): Promise>; /** Summary: Deletes orphaned files @@ -530,7 +559,6 @@ determined by bucket configuration This method may return different T based on the response code: - 200: DeleteOrphanedFilesResponse200 - - 400: ErrorResponse */ deleteOrphanedFiles( options?: RequestInit, @@ -542,35 +570,41 @@ determined by bucket configuration This method may return different T based on the response code: - 200: ListBrokenMetadataResponse200 - - 400: ErrorResponse */ listBrokenMetadata( options?: RequestInit, ): Promise>; /** - Summary: Delete broken metadata - Broken metadata is defined as metadata that has isUploaded = true but there is no file in the storage matching it. This is an admin operation that requires the Hasura admin secret. + Summary: Lists files that haven't been uploaded + That is, metadata that has isUploaded = false. This is an admin operation that requires the Hasura admin secret. This method may return different T based on the response code: - - 200: DeleteBrokenMetadataResponse200 - - 400: ErrorResponse + - 200: ListFilesNotUploadedResponse200 */ - deleteBrokenMetadata( + listFilesNotUploaded( options?: RequestInit, - ): Promise>; + ): Promise>; /** - Summary: Lists files that haven't been uploaded - That is, metadata that has isUploaded = false. This is an admin operation that requires the Hasura admin secret. + Summary: Lists orphaned files + Orphaned files are files that are present in the storage but have no associated metadata. This is an admin operation that requires the Hasura admin secret. This method may return different T based on the response code: - - 200: ListFilesNotUploadedResponse200 - - 400: ErrorResponse + - 200: ListOrphanedFilesResponse200 */ - listFilesNotUploaded( + listOrphanedFiles( options?: RequestInit, - ): Promise>; + ): Promise>; + + /** + Summary: Get service version information + Retrieves build and version information about the storage service. Useful for monitoring and debugging. + + This method may return different T based on the response code: + - 200: VersionInformation + */ + getVersion(options?: RequestInit): Promise>; } export const createAPIClient = ( @@ -583,38 +617,6 @@ export const createAPIClient = ( chainFunctions.push(chainFunction); fetch = createEnhancedFetch(chainFunctions); }; - const getVersion = async ( - options?: RequestInit, - ): Promise> => { - const url = baseURL + `/version`; - const res = await fetch(url, { - ...options, - method: "GET", - headers: { - ...options?.headers, - }, - }); - - if (res.status >= 300) { - const responseBody = [412].includes(res.status) ? null : await res.text(); - const payload: unknown = responseBody ? JSON.parse(responseBody) : {}; - throw new FetchError(payload, res.status, res.headers); - } - - const responseBody = [204, 205, 304].includes(res.status) - ? null - : await res.text(); - const payload: VersionInformation = responseBody - ? JSON.parse(responseBody) - : {}; - - return { - body: payload, - status: res.status, - headers: res.headers, - } as FetchResponse; - }; - const uploadFiles = async ( body: UploadFilesBody, options?: RequestInit, @@ -626,7 +628,11 @@ export const createAPIClient = ( } if (body["metadata[]"] !== undefined) { body["metadata[]"].forEach((value) => - formData.append("metadata[]", JSON.stringify(value)), + formData.append( + "metadata[]", + new Blob([JSON.stringify(value)], { type: "application/json" }), + "", + ), ); } if (body["file[]"] !== undefined) { @@ -659,11 +665,39 @@ export const createAPIClient = ( } as FetchResponse; }; - const getFileMetadataHeaders = async ( + const deleteFile = async ( id: string, - params?: GetFileMetadataHeadersParams, options?: RequestInit, ): Promise> => { + const url = baseURL + `/files/${id}`; + const res = await fetch(url, { + ...options, + method: "DELETE", + headers: { + ...options?.headers, + }, + }); + + if (res.status >= 300) { + const responseBody = [412].includes(res.status) ? null : await res.text(); + const payload: unknown = responseBody ? JSON.parse(responseBody) : {}; + throw new FetchError(payload, res.status, res.headers); + } + + const payload: void = undefined; + + return { + body: payload, + status: res.status, + headers: res.headers, + } as FetchResponse; + }; + + const getFile = async ( + id: string, + params?: GetFileParams, + options?: RequestInit, + ): Promise> => { const encodedParameters = params && Object.entries(params) @@ -682,7 +716,7 @@ export const createAPIClient = ( : baseURL + `/files/${id}`; const res = await fetch(url, { ...options, - method: "HEAD", + method: "GET", headers: { ...options?.headers, }, @@ -694,20 +728,20 @@ export const createAPIClient = ( throw new FetchError(payload, res.status, res.headers); } - const payload: void = undefined; + const payload: Blob = await res.blob(); return { body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const getFile = async ( + const getFileMetadataHeaders = async ( id: string, - params?: GetFileParams, + params?: GetFileMetadataHeadersParams, options?: RequestInit, - ): Promise> => { + ): Promise> => { const encodedParameters = params && Object.entries(params) @@ -726,7 +760,7 @@ export const createAPIClient = ( : baseURL + `/files/${id}`; const res = await fetch(url, { ...options, - method: "GET", + method: "HEAD", headers: { ...options?.headers, }, @@ -738,13 +772,13 @@ export const createAPIClient = ( throw new FetchError(payload, res.status, res.headers); } - const payload: Blob = await res.blob(); + const payload: void = undefined; return { body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; const replaceFile = async ( @@ -755,7 +789,13 @@ export const createAPIClient = ( const url = baseURL + `/files/${id}`; const formData = new FormData(); if (body["metadata"] !== undefined) { - formData.append("metadata", JSON.stringify(body["metadata"])); + formData.append( + "metadata", + new Blob([JSON.stringify(body["metadata"])], { + type: "application/json", + }), + "", + ); } if (body["file"] !== undefined) { formData.append("file", body["file"]); @@ -785,14 +825,14 @@ export const createAPIClient = ( } as FetchResponse; }; - const deleteFile = async ( + const getFilePresignedURL = async ( id: string, options?: RequestInit, - ): Promise> => { - const url = baseURL + `/files/${id}`; + ): Promise> => { + const url = baseURL + `/files/${id}/presignedurl`; const res = await fetch(url, { ...options, - method: "DELETE", + method: "GET", headers: { ...options?.headers, }, @@ -804,23 +844,27 @@ export const createAPIClient = ( throw new FetchError(payload, res.status, res.headers); } - const payload: void = undefined; + const responseBody = [204, 205, 304].includes(res.status) + ? null + : await res.text(); + const payload: PresignedURLResponse = responseBody + ? JSON.parse(responseBody) + : {}; return { body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const getPresignedURL = async ( - id: string, + const deleteBrokenMetadata = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/files/${id}/presignedurl`; + ): Promise> => { + const url = baseURL + `/ops/delete-broken-metadata`; const res = await fetch(url, { ...options, - method: "GET", + method: "POST", headers: { ...options?.headers, }, @@ -835,7 +879,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: PresignedURLResponse = responseBody + const payload: DeleteBrokenMetadataResponse200 = responseBody ? JSON.parse(responseBody) : {}; @@ -843,13 +887,13 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const listOrphanedFiles = async ( + const deleteOrphanedFiles = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/ops/list-orphans`; + ): Promise> => { + const url = baseURL + `/ops/delete-orphans`; const res = await fetch(url, { ...options, method: "POST", @@ -867,7 +911,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: ListOrphanedFilesResponse200 = responseBody + const payload: DeleteOrphanedFilesResponse200 = responseBody ? JSON.parse(responseBody) : {}; @@ -875,13 +919,13 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const deleteOrphanedFiles = async ( + const listBrokenMetadata = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/ops/delete-orphans`; + ): Promise> => { + const url = baseURL + `/ops/list-broken-metadata`; const res = await fetch(url, { ...options, method: "POST", @@ -899,7 +943,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: DeleteOrphanedFilesResponse200 = responseBody + const payload: ListBrokenMetadataResponse200 = responseBody ? JSON.parse(responseBody) : {}; @@ -907,13 +951,13 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const listBrokenMetadata = async ( + const listFilesNotUploaded = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/ops/list-broken-metadata`; + ): Promise> => { + const url = baseURL + `/ops/list-not-uploaded`; const res = await fetch(url, { ...options, method: "POST", @@ -931,7 +975,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: ListBrokenMetadataResponse200 = responseBody + const payload: ListFilesNotUploadedResponse200 = responseBody ? JSON.parse(responseBody) : {}; @@ -939,13 +983,13 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const deleteBrokenMetadata = async ( + const listOrphanedFiles = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/ops/delete-broken-metadata`; + ): Promise> => { + const url = baseURL + `/ops/list-orphans`; const res = await fetch(url, { ...options, method: "POST", @@ -963,7 +1007,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: DeleteBrokenMetadataResponse200 = responseBody + const payload: ListOrphanedFilesResponse200 = responseBody ? JSON.parse(responseBody) : {}; @@ -971,16 +1015,16 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; - const listFilesNotUploaded = async ( + const getVersion = async ( options?: RequestInit, - ): Promise> => { - const url = baseURL + `/ops/list-not-uploaded`; + ): Promise> => { + const url = baseURL + `/version`; const res = await fetch(url, { ...options, - method: "POST", + method: "GET", headers: { ...options?.headers, }, @@ -995,7 +1039,7 @@ export const createAPIClient = ( const responseBody = [204, 205, 304].includes(res.status) ? null : await res.text(); - const payload: ListFilesNotUploadedResponse200 = responseBody + const payload: VersionInformation = responseBody ? JSON.parse(responseBody) : {}; @@ -1003,23 +1047,23 @@ export const createAPIClient = ( body: payload, status: res.status, headers: res.headers, - } as FetchResponse; + } as FetchResponse; }; return { baseURL, pushChainFunction, - getVersion, uploadFiles, - getFileMetadataHeaders, + deleteFile, getFile, + getFileMetadataHeaders, replaceFile, - deleteFile, - getPresignedURL, - listOrphanedFiles, + getFilePresignedURL, + deleteBrokenMetadata, deleteOrphanedFiles, listBrokenMetadata, - deleteBrokenMetadata, listFilesNotUploaded, + listOrphanedFiles, + getVersion, }; }; diff --git a/tools/codegen/processor/testdata/methods_ref.yaml.ts b/tools/codegen/processor/testdata/methods_ref.yaml.ts index 79a7c5d7..9045b872 100644 --- a/tools/codegen/processor/testdata/methods_ref.yaml.ts +++ b/tools/codegen/processor/testdata/methods_ref.yaml.ts @@ -768,7 +768,11 @@ export const createAPIClient = ( } if (body["metadata[]"] !== undefined) { body["metadata[]"].forEach((value) => - formData.append("metadata[]", JSON.stringify(value)), + formData.append( + "metadata[]", + new Blob([JSON.stringify(value)], { type: "application/json" }), + "", + ), ); } if (body["file[]"] !== undefined) { @@ -903,7 +907,11 @@ export const createAPIClient = ( const url = baseURL + `/files/${id}`; const formData = new FormData(); if (body["metadata"] !== undefined) { - formData.append("metadata", JSON.stringify(body["metadata"])); + formData.append( + "metadata", + new Blob([JSON.stringify(body["metadata"])], { type: "application/json" }), + "", + ); } if (body["file"] !== undefined) { formData.append("file", body["file"]); diff --git a/tools/codegen/processor/typescript/templates/client.tmpl b/tools/codegen/processor/typescript/templates/client.tmpl index e150ae53..0dd808ad 100644 --- a/tools/codegen/processor/typescript/templates/client.tmpl +++ b/tools/codegen/processor/typescript/templates/client.tmpl @@ -134,7 +134,11 @@ export const createAPIClient = ( {{- if eq .Type.Item.Kind "scalar" }} formData.append("{{ .Name }}", value), {{- else if eq .Type.Item.Kind "object" }} - formData.append("{{ .Name }}", JSON.stringify(value)), + formData.append( + "{{ .Name }}", + new Blob([JSON.stringify(value)], { type: "application/json" }), + "", + ), {{- else }} TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }} {{- end }} @@ -142,7 +146,11 @@ export const createAPIClient = ( } {{- else if eq .Type.Kind "object" }} if (body["{{ .Name }}"] !== undefined) { - formData.append("{{ .Name }}", JSON.stringify(body["{{ .Name }}"])); + formData.append( + "{{ .Name }}", + new Blob([JSON.stringify(body["{{ .Name }}"])], { type: "application/json" }), + "", + ); } {{- else }} TODO {{ .Type.Kind }} {{ .Type.Schema.Schema.Type }} From add226ab428d91662d0c756ba7339ba46118627e Mon Sep 17 00:00:00 2001 From: David Barroso Date: Mon, 11 Aug 2025 14:20:18 +0200 Subject: [PATCH 3/7] asdasd --- .golangci.bck.yaml | 79 +++++++++ .golangci.yaml | 163 ++++++++++-------- docs/CHANGELOG.md | 1 + packages/nhost-js/CHANGELOG.md | 1 + tools/codegen/.golangci.yaml | 55 ++++++ tools/codegen/CHANGELOG.md | 1 + tools/codegen/cmd/gen/gen.go | 5 +- tools/codegen/format/format.go | 4 + tools/codegen/processor/intermediate.go | 11 ++ tools/codegen/processor/intermediate_test.go | 1 + tools/codegen/processor/methods.go | 34 +++- tools/codegen/processor/types.go | 8 + .../processor/typescript/typescript.go | 3 + tools/codegen/project.nix | 2 +- 14 files changed, 289 insertions(+), 79 deletions(-) create mode 100644 .golangci.bck.yaml create mode 100644 tools/codegen/.golangci.yaml diff --git a/.golangci.bck.yaml b/.golangci.bck.yaml new file mode 100644 index 00000000..6506a3f7 --- /dev/null +++ b/.golangci.bck.yaml @@ -0,0 +1,79 @@ +linters: + enable-all: true + disable: + - varnamelen + - gomoddirectives + - nlreturn + - wsl + - musttag # FIXME + - depguard + - canonicalheader + - tenv # deprecated + +linters-settings: + ireturn: + allow: + - anon + - error + - empty + - generic + - stdlib + +issues: + exclude-rules: + - path: _test\.go + linters: + - funlen + - ireturn + - goconst + - revive + + - linters: + - lll + source: "^//go:generate " + + - linters: + - gochecknoglobals + text: "Version is a global variable" + + - path: schema\.resolvers\.go + linters: + - ireturn + - lll + - gofumpt + + - path: services/watchtower/pkg + linters: + - perfsprint + - nolintlint + - testpackage + - paralleltest + - wrapcheck + - thelper + - tagliatelle + - stylecheck + - ireturn + - mnd + - goerr113 + - err113 + - gochecknoglobals + - exhaustruct + - nilnil + - exhaustive + - forcetypeassert + - containedctx + - unparam + - revive + - prealloc + - nestif + - funlen + - misspell + - lll + - godox + - errname + - cyclop + - godot + - gci + - gosec + - gocognit + - dupl diff --git a/.golangci.yaml b/.golangci.yaml index 6506a3f7..9122e013 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,79 +1,94 @@ +version: "2" linters: - enable-all: true + default: all disable: - - varnamelen + - canonicalheader + - depguard - gomoddirectives + - musttag - nlreturn + - varnamelen - wsl - - musttag # FIXME - - depguard - - canonicalheader - - tenv # deprecated - -linters-settings: - ireturn: - allow: - - anon - - error - - empty - - generic - - stdlib - -issues: - exclude-rules: - - path: _test\.go - linters: - - funlen - - ireturn - - goconst - - revive - - - linters: - - lll - source: "^//go:generate " - - - linters: - - gochecknoglobals - text: "Version is a global variable" - - - path: schema\.resolvers\.go - linters: - - ireturn - - lll - - gofumpt - - - path: services/watchtower/pkg - linters: - - perfsprint - - nolintlint - - testpackage - - paralleltest - - wrapcheck - - thelper - - tagliatelle - - stylecheck - - ireturn - - mnd - - goerr113 - - err113 - - gochecknoglobals - - exhaustruct - - nilnil - - exhaustive - - forcetypeassert - - containedctx - - unparam - - revive - - prealloc - - nestif - - funlen - - misspell - - lll - - godox - - errname - - cyclop - - godot - - gci - - gosec - - gocognit - - dupl + settings: + ireturn: + allow: + - anon + - error + - empty + - generic + - stdlib + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - funlen + - goconst + - ireturn + - revive + path: _test\.go + - linters: + - lll + source: '^//go:generate ' + - linters: + - gochecknoglobals + text: Version is a global variable + - linters: + - ireturn + - lll + path: schema\.resolvers\.go + - linters: + - containedctx + - cyclop + - dupl + - err113 + - errname + - exhaustive + - exhaustruct + - forcetypeassert + - funlen + - gochecknoglobals + - gocognit + - godot + - godox + - gosec + - ireturn + - lll + - misspell + - mnd + - nestif + - nilnil + - nolintlint + - paralleltest + - perfsprint + - prealloc + - revive + - staticcheck + - tagliatelle + - testpackage + - thelper + - unparam + - wrapcheck + path: services/watchtower/pkg + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + - schema\.resolvers\.go + - services/watchtower/pkg diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 62ca74ee..1decd87a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,7 @@ ## main +- 20250811 feat (package/nhost-js): update openapi specs and properly generate formData when uploading/updating files #47 - 20250705 feat chore (nhost-js): update openapi #44 - 20250704 feat (packages/nhost-js): added functions.post method and improve documentation #43 - 20250627 feat (packages/nhost-js): graphql.post -> graphql.request #41 diff --git a/packages/nhost-js/CHANGELOG.md b/packages/nhost-js/CHANGELOG.md index 369997c0..01e462b2 100644 --- a/packages/nhost-js/CHANGELOG.md +++ b/packages/nhost-js/CHANGELOG.md @@ -1,5 +1,6 @@ # dev +- feat (package/nhost-js): update openapi specs and properly generate formData when uploading/updating files #47 - chore (nhost-js): update openapi #44 - feat (packages/nhost-js): added functions.post method and improve documentation #43 - feat (packages/nhost-js): add decoded token to session storage #37 diff --git a/tools/codegen/.golangci.yaml b/tools/codegen/.golangci.yaml new file mode 100644 index 00000000..eb20287c --- /dev/null +++ b/tools/codegen/.golangci.yaml @@ -0,0 +1,55 @@ +version: "2" +linters: + default: all + settings: + funlen: + lines: 65 + disable: + - canonicalheader + - depguard + - gomoddirectives + - musttag + - nlreturn + - tagliatelle + - varnamelen + - wsl + - noinlineerr + - funcorder + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - funlen + - ireturn + path: _test\.go + - linters: + - lll + source: '^//go:generate ' + - linters: + - gochecknoglobals + text: Version is a global variable + - linters: + - ireturn + - lll + path: schema\.resolvers\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + - schema\.resolvers\.go diff --git a/tools/codegen/CHANGELOG.md b/tools/codegen/CHANGELOG.md index 17293239..054eb9ad 100644 --- a/tools/codegen/CHANGELOG.md +++ b/tools/codegen/CHANGELOG.md @@ -1,3 +1,4 @@ +- 20250811 feat (package/nhost-js): update openapi specs and properly generate formData when uploading/updating files #47 - 20250704 feat (packages/nhost-js): added functions.post method and improve documentation #43 - 20250610 fix (tools/codegen;packages/nhost-js): encode URL parameters better #30 - 20250601 chore (ci): enabled #26 diff --git a/tools/codegen/cmd/gen/gen.go b/tools/codegen/cmd/gen/gen.go index 55ba20a8..64396731 100644 --- a/tools/codegen/cmd/gen/gen.go +++ b/tools/codegen/cmd/gen/gen.go @@ -49,6 +49,7 @@ func action(_ context.Context, c *cli.Command) error { fmt.Println("Generating code...") //nolint:forbidigo var p processor.Plugin + switch c.String(flagPlugin) { case "typescript": p = &typescript.Typescript{} @@ -65,11 +66,13 @@ func action(_ context.Context, c *cli.Command) error { if err != nil { return cli.Exit(fmt.Sprintf("failed to parse OpenAPI document: %v", err), 1) } + docModel, errors := document.BuildV3Model() if len(errors) > 0 { for i := range errors { fmt.Printf("error: %e\n", errors[i]) //nolint:forbidigo } + return cli.Exit("failed to build OpenAPI model", 1) } @@ -86,7 +89,7 @@ func action(_ context.Context, c *cli.Command) error { if err != nil { return cli.Exit(fmt.Sprintf("failed to open output file: %v", err), 1) } - defer f.Close() + defer f.Close() //nolint:errcheck if err := ir.Render(f); err != nil { return cli.Exit(fmt.Sprintf("failed to write output: %v", err), 1) diff --git a/tools/codegen/format/format.go b/tools/codegen/format/format.go index 01597219..ffa3c7b3 100644 --- a/tools/codegen/format/format.go +++ b/tools/codegen/format/format.go @@ -14,8 +14,10 @@ func Title(s string) string { if len(s) == 0 { return s // return empty string if input is empty } + r := []rune(s) r[0] = unicode.ToUpper(r[0]) + return string(r) } @@ -23,6 +25,7 @@ func AntiTitle(s string) string { if len(s) == 0 { return s } + return strings.ToLower(string(s[0])) + s[1:] } @@ -35,5 +38,6 @@ func ToCamelCase(s string) string { for i := range parts { parts[i] = Title(parts[i]) } + return strings.Join(parts, "") } diff --git a/tools/codegen/processor/intermediate.go b/tools/codegen/processor/intermediate.go index d2633b4b..d30c19a2 100644 --- a/tools/codegen/processor/intermediate.go +++ b/tools/codegen/processor/intermediate.go @@ -39,6 +39,7 @@ func NewInterMediateRepresentation( if doc.Model.Components != nil && doc.Model.Components.Schemas != nil { var err error + types, err = newInterMediateRepresentationComponentsSchemas( doc.Model.Components.Schemas, plugin, ) @@ -62,15 +63,18 @@ func NewInterMediateRepresentation( err, ) } + types = append(types, types2...) } var methods []*Method + if doc.Model.Paths != nil { m, types2, err := newInterMediateRepresentationPaths(doc, plugin) if err != nil { return nil, fmt.Errorf("failed to create intermediate representation paths: %w", err) } + types = append(types, types2...) methods = m } @@ -86,6 +90,7 @@ func newInterMediateRepresentationComponentsSchemas( schemas *orderedmap.Map[string, *base.SchemaProxy], plugin Plugin, ) ([]Type, error) { types := make([]Type, 0, 10) //nolint:mnd + for schemaPairs := schemas.First(); schemaPairs != nil; schemaPairs = schemaPairs.Next() { schemaName := schemaPairs.Key() proxy := schemaPairs.Value() @@ -102,6 +107,7 @@ func newInterMediateRepresentationComponentsSchemas( return nil, fmt.Errorf("%w: schema %s is not an object", ErrUnknownType, schemaName) } } + return types, nil } @@ -109,6 +115,7 @@ func newInterMediateRepresentationComponentsParameters( schemas *orderedmap.Map[string, *v3.Parameter], plugin Plugin, ) ([]Type, error) { types := make([]Type, 0, 10) //nolint:mnd + for paramPairs := schemas.First(); paramPairs != nil; paramPairs = paramPairs.Next() { schemaName := paramPairs.Key() proxy := paramPairs.Value() @@ -120,6 +127,7 @@ func newInterMediateRepresentationComponentsParameters( types = append(types, tt...) } + return types, nil } @@ -128,6 +136,7 @@ func newInterMediateRepresentationPaths( ) ([]*Method, []Type, error) { methods := make([]*Method, 0, 10) //nolint:mnd types := make([]Type, 0, 10) //nolint:mnd + for pathPairs := doc.Model.Paths.PathItems.First(); pathPairs != nil; pathPairs = pathPairs.Next() { path := pathPairs.Key() item := pathPairs.Value() @@ -141,6 +150,7 @@ func newInterMediateRepresentationPaths( if err != nil { return nil, nil, fmt.Errorf("failed to create method for path %s: %w", path, err) } + methods = append(methods, m) types = append(types, tt...) } @@ -158,6 +168,7 @@ func (ir *InterMediateRepresentation) Render(out io.Writer) error { } var filenames []string + for _, entry := range entries { if !entry.IsDir() { filenames = append(filenames, "templates/"+entry.Name()) diff --git a/tools/codegen/processor/intermediate_test.go b/tools/codegen/processor/intermediate_test.go index abc4d59b..07b92074 100644 --- a/tools/codegen/processor/intermediate_test.go +++ b/tools/codegen/processor/intermediate_test.go @@ -32,6 +32,7 @@ func getModel(filepath string) (*libopenapi.DocumentModel[v3.Document], error) { for i := range errorsList { wrappedError = errors.Join(wrappedError, errorsList[i]) } + return nil, fmt.Errorf("cannot create v3 model from document: %w", wrappedError) } diff --git a/tools/codegen/processor/methods.go b/tools/codegen/processor/methods.go index 1709a75e..47bd2d72 100644 --- a/tools/codegen/processor/methods.go +++ b/tools/codegen/processor/methods.go @@ -51,6 +51,7 @@ func (m *Method) PathParameters() []*Parameter { params = append(params, param) } } + return params } @@ -60,6 +61,7 @@ func (m *Method) HasQueryParameters() bool { return true } } + return false } @@ -70,6 +72,7 @@ func (m *Method) QueryParameters() []*Parameter { params = append(params, param) } } + return params } @@ -81,11 +84,13 @@ func addIfNotPresent[S ~[]E, E comparable](s S, v E) S { if !slices.Contains(s, v) { return append(s, v) } + return s } func (m *Method) ReturnType() string { tt := make([]string, 0, 10) //nolint:mnd + for c, resp := range m.Responses { code, err := strconv.Atoi(c) if err != nil { @@ -109,6 +114,7 @@ func (m *Method) ReturnType() string { } } } + return strings.Join(tt, " | ") } @@ -118,6 +124,7 @@ func (m *Method) RequestJSON() Type { //nolint:ireturn return t } } + return nil } @@ -127,6 +134,7 @@ func (m *Method) RequestFormData() Type { //nolint:ireturn return t } } + return nil } @@ -149,6 +157,7 @@ func (m *Method) ResponseJSON() bool { return media == mediaApplicationJSON } } + return false } @@ -167,6 +176,7 @@ func (m *Method) ResponseBinary() bool { return media == mediaApplicationOctetStream } } + return false } @@ -174,6 +184,7 @@ func (m *Method) IsRedirect() bool { for code := range m.Responses { return code == strconv.Itoa(http.StatusFound) } + return false } @@ -190,6 +201,7 @@ func (m *Method) HasResponseBody() bool { return len(resp) > 0 } + return false } @@ -208,9 +220,11 @@ func (p *Parameter) Required() bool { if p.Parameter.Required != nil { return *p.Parameter.Required } + if p.Parameter.In == "path" { return true } + return false } @@ -229,6 +243,7 @@ func GetMethod( path, ) } + params, types, err := getMethodParameters(method, operation, p) if err != nil { return nil, nil, fmt.Errorf( @@ -292,6 +307,7 @@ func getMethodParameters( if err != nil { return nil, nil, fmt.Errorf("failed to get type for parameter %s: %w", param.Name, err) } + types = append(types, tt...) t = t2 case param.Content != nil: @@ -303,16 +319,19 @@ func getMethodParameters( operation.OperationId, ) } + t2, tt, err := GetType(jsonMediaType.Schema, method+format.Title(param.Name), p, false) if err != nil { return nil, nil, fmt.Errorf("failed to get type for parameter %s: %w", param.Name, err) } + types = append(types, tt...) t = t2 default: - return nil, nil, fmt.Errorf("parameter %s in operation %s has no schema or content defined", param.Name, operation.OperationId) //nolint:goerr113,lll + return nil, nil, fmt.Errorf("parameter %s in operation %s has no schema or content defined", param.Name, operation.OperationId) //nolint:err113,lll } } + params[i] = &Parameter{ name: param.Name, Parameter: param, @@ -349,13 +368,18 @@ func getMethodBodies( } var tt []Type + for pair := operation.RequestBody.Content.First(); pair != nil; pair = pair.Next() { mediaType := pair.Key() proxy := pair.Value() name := operation.OperationId + "Body" - var t Type - var err error + + var ( + t Type + err error + ) + t, tt, err = GetType(proxy.Schema, name, p, false) if err != nil { return nil, nil, fmt.Errorf( @@ -364,6 +388,7 @@ func getMethodBodies( err, ) } + bodies[mediaType] = t } @@ -408,6 +433,7 @@ func getMethodResponses( } name := operation.OperationId + "Response" + code + t, tt, err := GetType(proxy.Schema, name, p, false) if err != nil { return nil, nil, fmt.Errorf( @@ -416,7 +442,9 @@ func getMethodResponses( err, ) } + responses[code][mediaType] = t + types = append(types, tt...) } diff --git a/tools/codegen/processor/types.go b/tools/codegen/processor/types.go index 7e373662..6380d654 100644 --- a/tools/codegen/processor/types.go +++ b/tools/codegen/processor/types.go @@ -198,6 +198,7 @@ func getTypeObject( //nolint:ireturn p: p, }, nil, nil } + return nil, nil, fmt.Errorf( "%w: object schema %s has no properties and no additional properties", ErrUnknownType, @@ -224,12 +225,14 @@ func getTypeArray(schema *base.SchemaProxy, p Plugin) (Type, []Type, error) { // if err != nil { return nil, nil, fmt.Errorf("failed to get type for array item: %w", err) } + return &TypeArray{ schema: schema, p: p, Item: t, }, nil, nil } + return &TypeArray{ schema: schema, p: p, @@ -258,6 +261,7 @@ func getTypeEnum( //nolint:ireturn if err := enum.Decode(&v); err != nil { return nil, nil, fmt.Errorf("failed to decode enum value %v: %w", v, err) } + values = append(values, v) } @@ -267,6 +271,7 @@ func getTypeEnum( //nolint:ireturn values: values, p: p, } + return t, []Type{t}, nil } @@ -298,8 +303,10 @@ func GetType( //nolint:ireturn alias: s, p: p, } + return t, []Type{t}, nil } + return s, nil, nil } } @@ -324,6 +331,7 @@ func NewObject( prop := propPairs.Value() derivedName := name + format.Title(propName) + typ, tt, err := GetType(prop, derivedName, p, false) if err != nil { return nil, nil, fmt.Errorf("failed to get type for property %s: %w", propName, err) diff --git a/tools/codegen/processor/typescript/typescript.go b/tools/codegen/processor/typescript/typescript.go index a45667d4..b27285bb 100644 --- a/tools/codegen/processor/typescript/typescript.go +++ b/tools/codegen/processor/typescript/typescript.go @@ -25,6 +25,7 @@ func quotePropertyIfNeeded(name string) string { if strings.Contains(name, "-") || strings.Contains(name, "[") { return fmt.Sprintf("\"%s\"", name) } + return name } @@ -47,6 +48,7 @@ func (t *Typescript) TypeScalarName(scalar *processor.TypeScalar) string { return "Blob" } } + return scalar.Schema().Schema().Type[0] } @@ -79,6 +81,7 @@ func (t *Typescript) TypeMapName(schema *processor.TypeMap) string { if v, ok := schema.Schema().Schema().Extensions.Get(extCustomType); ok { return v.Value } + return "Record" } diff --git a/tools/codegen/project.nix b/tools/codegen/project.nix index 54f19a54..7c368e08 100644 --- a/tools/codegen/project.nix +++ b/tools/codegen/project.nix @@ -8,10 +8,10 @@ let src = nix-filter.lib.filter { root = ../..; include = with nix-filter.lib;[ - ".golangci.yaml" "go.mod" "go.sum" (inDirectory "vendor") + "${submodule}/.golangci.yaml" isDirectory (and (inDirectory submodule) From a5178849273c61555c77e9ec5ab9ae7d1d42da80 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Mon, 11 Aug 2025 14:28:09 +0200 Subject: [PATCH 4/7] asd --- tools/codegen/cmd/gen/gen.go | 2 +- tools/codegen/processor/methods.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/codegen/cmd/gen/gen.go b/tools/codegen/cmd/gen/gen.go index 64396731..cca99d2e 100644 --- a/tools/codegen/cmd/gen/gen.go +++ b/tools/codegen/cmd/gen/gen.go @@ -89,7 +89,7 @@ func action(_ context.Context, c *cli.Command) error { if err != nil { return cli.Exit(fmt.Sprintf("failed to open output file: %v", err), 1) } - defer f.Close() //nolint:errcheck + defer f.Close() if err := ir.Render(f); err != nil { return cli.Exit(fmt.Sprintf("failed to write output: %v", err), 1) diff --git a/tools/codegen/processor/methods.go b/tools/codegen/processor/methods.go index 47bd2d72..027b4cdd 100644 --- a/tools/codegen/processor/methods.go +++ b/tools/codegen/processor/methods.go @@ -80,7 +80,7 @@ func (m *Method) QueryParametersTypeName() string { return m.p.TypeObjectName(m.Name() + "Parameters") } -func addIfNotPresent[S ~[]E, E comparable](s S, v E) S { +func addIfNotPresent[S ~[]E, E comparable](s S, v E) S { //nolint:ireturn if !slices.Contains(s, v) { return append(s, v) } From 9809c61595abc38f72686a77fe208d7ab9d90617 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Tue, 12 Aug 2025 09:31:48 +0200 Subject: [PATCH 5/7] asd --- backend/nhost/nhost.toml | 2 +- packages/nhost-js/api/storage.yaml | 2 +- packages/nhost-js/src/__tests__/storage.test.ts | 2 +- packages/nhost-js/src/storage/client.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/nhost/nhost.toml b/backend/nhost/nhost.toml index 3dc2163a..ff40c4eb 100644 --- a/backend/nhost/nhost.toml +++ b/backend/nhost/nhost.toml @@ -191,7 +191,7 @@ capacity = 1 [provider] [storage] -version = '0.8.0-beta1' +version = '0.8.0-beta3' [observability] [observability.grafana] diff --git a/packages/nhost-js/api/storage.yaml b/packages/nhost-js/api/storage.yaml index 085aff44..cddc4f3f 100644 --- a/packages/nhost-js/api/storage.yaml +++ b/packages/nhost-js/api/storage.yaml @@ -30,7 +30,7 @@ tags: description: System information paths: - /files/: + /files: post: summary: "Upload files" description: "Upload one or more files to a specified bucket. Supports batch uploading with optional custom metadata for each file. If uploading multiple files, either provide metadata for all files or none." diff --git a/packages/nhost-js/src/__tests__/storage.test.ts b/packages/nhost-js/src/__tests__/storage.test.ts index de6e41e0..75dc83eb 100644 --- a/packages/nhost-js/src/__tests__/storage.test.ts +++ b/packages/nhost-js/src/__tests__/storage.test.ts @@ -36,7 +36,7 @@ describe("Test Storage API", () => { expect(resp.status).toBe(200); expect(resp.body).toBeDefined(); - expect(resp.body.buildVersion).toBe("0.8.0-beta1"); + expect(resp.body.buildVersion).toBe("0.8.0-beta3"); }); it("should upload a file", async () => { diff --git a/packages/nhost-js/src/storage/client.ts b/packages/nhost-js/src/storage/client.ts index efcb0003..0be115fc 100644 --- a/packages/nhost-js/src/storage/client.ts +++ b/packages/nhost-js/src/storage/client.ts @@ -621,7 +621,7 @@ export const createAPIClient = ( body: UploadFilesBody, options?: RequestInit, ): Promise> => { - const url = baseURL + `/files/`; + const url = baseURL + `/files`; const formData = new FormData(); if (body["bucket-id"] !== undefined) { formData.append("bucket-id", body["bucket-id"]); From 61955aff53b4f87f083b40bd30f531d06598bee0 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Tue, 12 Aug 2025 11:15:04 +0200 Subject: [PATCH 6/7] asd --- packages/nhost-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nhost-js/package.json b/packages/nhost-js/package.json index 6c4609c3..e78615f5 100644 --- a/packages/nhost-js/package.json +++ b/packages/nhost-js/package.json @@ -1,6 +1,6 @@ { "name": "@nhost/nhost-js", - "version": "5.0.0-beta.7", + "version": "5.0.0-beta.8", "description": "Nhost JavaScript SDK", "main": "./dist/nhost-js.cjs.js", "module": "./dist/nhost-js.es.js", From f287461d9bc47fef2e40caf22239f7664a2b6148 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Wed, 13 Aug 2025 12:02:12 +0200 Subject: [PATCH 7/7] asd --- .../reference/javascript/nhost-js/storage.mdx | 10 ++++++ packages/nhost-js/api/storage.yaml | 31 ++++++++++++------- packages/nhost-js/src/storage/client.ts | 5 +++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/docs/reference/javascript/nhost-js/storage.mdx b/docs/reference/javascript/nhost-js/storage.mdx index 0b826283..8b75e924 100644 --- a/docs/reference/javascript/nhost-js/storage.mdx +++ b/docs/reference/javascript/nhost-js/storage.mdx @@ -1072,6 +1072,16 @@ type OutputImageFormat = "auto" | "same" | "jpeg" | "webp" | "png" | "avif"; Output format for image files. Use 'auto' for content negotiation based on Accept header +--- + +### RFC2822Date + +```ts +type RFC2822Date = string; +``` + +Date in RFC 2822 format + ## Functions ### createAPIClient() diff --git a/packages/nhost-js/api/storage.yaml b/packages/nhost-js/api/storage.yaml index cddc4f3f..de3abb00 100644 --- a/packages/nhost-js/api/storage.yaml +++ b/packages/nhost-js/api/storage.yaml @@ -141,14 +141,12 @@ paths: description: "Only return the file if it has been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: if-unmodified-since description: "Only return the file if it has not been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: q description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query @@ -329,14 +327,12 @@ paths: description: "Only return the file if it has been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: if-unmodified-since description: "Only return the file if it has not been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: q description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query @@ -588,6 +584,12 @@ paths: in: query schema: type: string + - name: X-Amz-Security-Token + description: Use presignedurl endpoint to generate this automatically + required: false + in: query + schema: + type: string - name: x-id description: Use presignedurl endpoint to generate this automatically required: true @@ -608,14 +610,12 @@ paths: description: "Only return the file if it has been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: if-unmodified-since description: "Only return the file if it has not been modified after the given date" in: header schema: - type: string - format: date-time + $ref: "#/components/schemas/RFC2822Date" - name: q description: "Image quality (1-100). Only applies to JPEG, WebP and PNG files" in: query @@ -964,6 +964,13 @@ components: description: "Hasura admin secret key for backend/administrative operations." schemas: + RFC2822Date: + type: string + description: "Date in RFC 2822 format" + pattern: '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{1,2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} \w+$' + example: "Tue, 12 Aug 2025 12:03:50 GMT" + x-go-type: Time + ErrorResponse: type: object description: "Error information returned by the API." diff --git a/packages/nhost-js/src/storage/client.ts b/packages/nhost-js/src/storage/client.ts index 0be115fc..623a7ac6 100644 --- a/packages/nhost-js/src/storage/client.ts +++ b/packages/nhost-js/src/storage/client.ts @@ -5,6 +5,11 @@ import { FetchError, createEnhancedFetch } from "../fetch"; import type { ChainFunction, FetchResponse } from "../fetch"; +/** + * Date in RFC 2822 format + */ +export type RFC2822Date = string; + /** * Error details. @property message (`string`) - Human-readable error message.