UNPKG

ngx-appwrite

Version:

A wrapper around the Appwrite WebSDK for easier implementation in Angular 16+ projects. The goal is to make the whole SDK accessible as well as provide some convenience functionality like RxJS streams where appropriate.

1,326 lines (1,320 loc) 78.6 kB
import * as i0 from '@angular/core'; import { InjectionToken, APP_INITIALIZER, Injectable, inject } from '@angular/core'; import { Client, Account as Account$1, ID, AuthenticatorType, Databases as Databases$1, Query, Avatars, Functions, Locale, Messaging, Storage, Teams } from 'appwrite'; export { ID } from 'appwrite'; import { Observable, of, shareReplay, switchMap, startWith, Subject, merge, debounceTime, map, distinctUntilChanged } from 'rxjs'; const watch = (client, channel, events) => { const observable = new Observable((observer) => { try { client.subscribe(channel, (response) => { if (!events) { observer.next(response); } else if ((typeof events === 'string' && response.events.includes(events)) || intersection(response.events, events)) { observer.next(response); } }); } catch (error) { console.error('Error while watching channel: ', channel); if (error instanceof Error) observer.error(error.message); } }); return observable; }; const wait = (seconds) => { return new Promise((resolve) => { setTimeout(resolve, seconds * 1000); }); }; const deepEqual = (obj1, obj2) => { return JSON.stringify(obj1) === JSON.stringify(obj2); }; function intersection(...args) { const arrays = args.map((arg) => Array.isArray(arg) ? arg : [arg]); if (arrays.length === 0) { return []; } const firstArray = arrays[0]; const uniqueValues = new Set(firstArray); for (let i = 1; i < arrays.length; i++) { const currentArray = arrays[i]; for (let j = 0; j < currentArray.length; j++) { const value = currentArray[j]; if (!uniqueValues.has(value)) { uniqueValues.delete(value); } } } return Array.from(uniqueValues); } let __client; let __defaultDatabaseId; const CLIENT = () => { if (!__client) { throw new Error('Appwrite client not initialized, did you call initializeAppwrite?'); } return __client; }; const DEFAULT_DATABASE_ID = () => { if (!__defaultDatabaseId) { console.warn('Appwrite default database id not initialized, can be passed inside provideAppwrite'); } return __defaultDatabaseId; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const ConfigToken = new InjectionToken('APPWRITE_USER_CONFIG'); const initializeAppwrite = (config) => { return () => { __client = new Client(); __client.setEndpoint(config.endpoint).setProject(config.project); // if (config.defaultDatabase) { console.log('Configured defaultDatabaseId: ', config.defaultDatabase); __defaultDatabaseId = config.defaultDatabase; } }; }; const provideAppwrite = (config) => { return [ { provide: ConfigToken, useValue: config, }, { provide: APP_INITIALIZER, useFactory: initializeAppwrite, multi: true, deps: [ConfigToken], }, ]; }; class Account { /* -------------------------------------------------------------------------- */ /* Setup */ /* -------------------------------------------------------------------------- */ _account; _client$ = of(CLIENT()).pipe(shareReplay(1)); _watchAuthChannel$ = this._client$.pipe(switchMap((client) => watch(client, 'account').pipe(startWith(null)))); _triggerManualAuthCheck$ = new Subject(); _auth$; /* -------------------------------------------------------------------------- */ /* Reactive */ /* -------------------------------------------------------------------------- */ constructor() { this._account = new Account$1(CLIENT()); } onAuth() { try { if (!this._auth$) { this._auth$ = merge(this._watchAuthChannel$, this._triggerManualAuthCheck$).pipe(switchMap(() => this._checkIfAuthExists()), debounceTime(50), map((account) => { if (!account) return null; return account; }), distinctUntilChanged(deepEqual), shareReplay(1)); } return this._auth$; } catch (error) { console.error('Error in Account > onAuth'); throw error; } } /* -------------------------------------------------------------------------- */ /* Default API - https://appwrite.io/docs/client/account?sdk=web-default */ /* -------------------------------------------------------------------------- */ /** * Get Account * * Get currently logged in user data as JSON object. * * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async get() { try { const account = await this._account.get(); return account; } catch (error) { console.error('Error fetching account'); throw error; } } /** * Create Account * * Use this endpoint to allow a new user to register a new account in your project. * After the user registration completes successfully, you can use the /account/verfication route * to start verifying the user email address. To allow the new user to login to their new account, * you need to create a new account session. * * @param {string} email * @param {string} password * @param {Models.Preferences} defaultPrefs * @param {string} name * @param {string} userId * Defaults to ID.unique() * @throws {AppwriteException} * @returns {Promise<Models.User<T>>} */ async create(email, password, defaultPrefs = {}, name, userId = ID.unique()) { const account = await this._account.create(userId, email, password, name); this.triggerAuthCheck(); await this.updatePrefs(defaultPrefs); return account; } /** * Update Email * * Update currently logged in user account email address. * After changing user address, the user confirmation status will get reset. * A new confirmation email is not sent automatically however you can use the * send confirmation email endpoint again to send the confirmation email. * For security measures, user password is required to complete this request. * This endpoint can also be used to convert an anonymous account to a normal one, * by passing an email address and a new password. * * * @param {string} email * @param {string} password * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updateEmail(email, password) { return this._account.updateEmail(email, password); } /** * List Identities * * Get the list of identities for the currently logged in user. * * * @param {string[]} queries * @throws {AppwriteException} * @returns {Promise<Models.IdentityList>} */ async listIdentities(queries = []) { return this._account.listIdentities(queries); } /** * Delete Identity * * Delete a user identity by id. * * * @param {string} id * @throws {AppwriteException} * @returns {Promise<{}>} */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type async deleteIdentity(id) { return this._account.deleteIdentity(id); } /** * Create JWT * * Use this endpoint to create a JSON Web Token. You can use the resulting JWT * to authenticate on behalf of the current user when working with the * Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes * from its creation and will be invalid if the user will logout in that time * frame. * * @throws {AppwriteException} * @returns {Promise<Models.Jwt>} */ async createJWT() { return await this._account.createJWT(); } /** * List Logs * * Get the list of latest security activity logs for the currently logged in user. * Each log returns user IP address, location and date and time of log. * * @param {string[]} queries * @throws {AppwriteException} * @returns {Promise<AppwriteLogListObject>} */ async listLogs(queries = []) { return this._account.listLogs(queries); } /** * Update MFA * * Enable or disable MFA on an account. * * @param {boolean} enableMFA * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updateMFA(enableMFA) { return this._account.updateMFA(enableMFA); } /** * Add Authenticator * * Add an authenticator app to be used as an MFA factor. * Verify the authenticator using the verify authenticator method. * * @throws {AppwriteException} * @returns {Promise<Models.MfaType>} */ async createMfaAuthenticator() { return this._account.createMfaAuthenticator(AuthenticatorType.Totp); } /** * Verify Authenticator * * Verify an authenticator app after adding it using the add authenticator method. * * @param {string} otp * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updateMfaAuthenticator(otp) { return this._account.updateMfaAuthenticator(AuthenticatorType.Totp, // type otp); } /** * Delete Authenticator * * Delete an authenticator for a user. * Verify the authenticator using the verify authenticator method. * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type async deleteMfaAuthenticator() { return this._account.deleteMfaAuthenticator(AuthenticatorType.Totp); } /** * Create 2FA Challenge * * Begin the process of MFA verification after sign-in. * Finish the flow with updateMfaChallenge method. * * @param {AuthenticationFactor} factor * @throws {AppwriteException} * @returns {Promise<Models.MfaChallenge>} */ async createMfaChallenge(factor) { return this._account.createMfaChallenge(factor); } /** * Create MFA Challenge (confirmation) * * Complete the MFA challenge by providing the one-time password. * Finish the process of MFA verification by providing the one-time password. * To begin the flow, use createMfaChallenge method. * * @param {string} challengeId * @param {string} otp * @throws {AppwriteException} * @returns {Promise<{}>} */ async updateMfaChallenge(challengeId, otp) { return this._account.updateMfaChallenge(challengeId, otp); } /** * List Factors * * List the factors available on the account to be used as a MFA challange. * * @throws {AppwriteException} * @returns {Promise<Models.MfaFactors>} */ async listMfaFactors() { return this._account.listMfaFactors(); } /** * Get MFA Recovery Codes * * Get recovery codes that can be used as backup for MFA flow. * Before getting codes, they must be generated using createMfaRecoveryCodes method. * An OTP challenge is required to read recovery codes. * * @throws {AppwriteException} * @returns {Promise<Models.MfaRecoveryCodes>} */ async getMfaRecoveryCodes() { return this._account.getMfaRecoveryCodes(); } /** * Create MFA Recovery Codes * * Generate recovery codes as backup for MFA flow. * It's recommended to generate and show then immediately after user * successfully adds their authehticator. Recovery codes can be used as a MFA * verification type in createMfaChallenge method. * * @throws {AppwriteException} * @returns {Promise<Models.MfaRecoveryCodes>} */ async createMfaRecoveryCodes() { return this._account.createMfaRecoveryCodes(); } /** * Regenerate MFA Recovery Codes * * Regenerate recovery codes that can be used as backup for MFA flow. * Before regenerating codes, they must be first generated using * createMfaRecoveryCodes method. An OTP challenge is required to regenreate * recovery codes. * * @throws {AppwriteException} * @returns {Promise<Models.MfaRecoveryCodes>} */ async updateMfaRecoveryCodes() { return this._account.updateMfaRecoveryCodes(); } /** * Update Name * * Update currently logged in user account name. * * @param {string} name * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updateName(name) { return this._account.updateName(name); } /** * Update Password * * Update currently logged in user password. For validation, user is required * to pass in the new password, and the old password. For users created with * OAuth, Team Invites and Magic URL, oldPassword is optional. * * @param {string} password * @param {string} oldPassword * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updatePassword(password, oldPassword) { return this._account.updatePassword(password, oldPassword); } /** * Update Phone * * Update the currently logged in user's phone number. After updating the * phone number, the phone verification status will be reset. A confirmation * SMS is not sent automatically, however you can use the [POST * /account/verification/phone](/docs/client/account#accountCreatePhoneVerification) * endpoint to send a confirmation SMS. * * @param {string} phoneNumber * @param {string} password * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updatePhone(phoneNumber, password) { return this._account.updatePhone(phoneNumber, password); } /** * Get Account Preferences * * Get the preferences as a key-value object for the currently logged in user. * * @throws {AppwriteException} * @returns {Promise<TPrefs>} */ async getPrefs() { return this._account.getPrefs(); } /** * Update Preferences * * Update currently logged in user account preferences. The object you pass is * stored as is, and replaces any previous value. The maximum allowed prefs * size is 64kB and throws error if exceeded. * * @param {object} prefs * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updatePrefs(prefs) { return this._account.updatePrefs(prefs); } /** * Create Password Recovery * * Sends the user an email with a temporary secret key for password reset. * When the user clicks the confirmation link he is redirected back to your * app password reset URL with the secret key and email address values * attached to the URL query string. Use the query string params to submit a * request to the [PUT * /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to * complete the process. The verification link sent to the user's email * address is valid for 1 hour. * * @param {string} email * @param {string} url * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createRecovery(email, url) { const result = await this._account.createRecovery(email, url); this.triggerAuthCheck(); return result; } /** * Create Password Recovery (confirmation) * * Use this endpoint to complete the user account password reset. Both the * **userId** and **secret** arguments will be passed as query parameters to * the redirect URL you have provided when sending your request to the [POST * /account/recovery](/docs/client/account#accountCreateRecovery) endpoint. * * Please note that in order to avoid a [Redirect * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) * the only valid redirect URLs are the ones from domains you have set when * adding your platforms in the console interface. * * @param {string} userId * @param {string} secret * @param {string} password * @throws {AppwriteException} * @returns {Promise<Models.Toke>} */ async updateRecovery(userId, secret, password) { const result = await this._account.updateRecovery(userId, secret, password); this.triggerAuthCheck(); return result; } /** * List Sessions * * Get currently logged in user list of active sessions across different * devices. * * @throws {AppwriteException} * @returns {Promise<Models.SessionList>} */ async listSessions() { return this._account.listSessions(); } /** * Delete Sessions * * Delete all sessions from the user account and remove any sessions cookies * from the end client. * * @throws {AppwriteException} * @returns {Promise<{}>} */ async deleteSessions() { const result = await this._account.deleteSessions(); this.triggerAuthCheck(); return result; } /** * Create Anonymous Session * * Use this endpoint to allow a new user to register an anonymous account in * your project. This route will also create a new session for the user. To * allow the new user to convert an anonymous account to a normal account, you * need to update its [email and * password](/docs/client/account#accountUpdateEmail) or create an [OAuth2 * session](/docs/client/account#accountCreateOAuth2Session). * * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async createAnonymousSession() { const session = await this._account.createAnonymousSession(); this.triggerAuthCheck(); return session; } /** * Create email password session * * Allow the user to login into their account by providing a valid email and * password combination. This route will create a new session for the user. * * A user is limited to 10 active sessions at a time by default. [Learn more * about session limits](/docs/authentication#limits). * * @param {string} email * @param {string} password * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async createEmailPasswordSession(email, password) { const session = await this._account.createEmailPasswordSession(email, password); this.triggerAuthCheck(); return session; } /** * Create Magic URL session (confirmation) * * Use this endpoint to create a session from token. * Provide the userId and secret parameters from the successful response * of authentication flows initiated by token creation. For example, * magic URL and phone login. * * @param {string} userId * @param {string} secret * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async updateMagicURLSession(userId, secret) { const session = await this._account.updateMagicURLSession(userId, secret); this.triggerAuthCheck(); return session; } /** * Create OAuth2 Session * * Allow the user to login to their account using the OAuth2 provider of their * choice. Each OAuth2 provider should be enabled from the Appwrite console * first. Use the success and failure arguments to provide a redirect URL's * back to your app when login is completed. * * If there is already an active session, the new session will be attached to * the logged-in account. If there are no active sessions, the server will * attempt to look for a user with the same email address as the email * received from the OAuth2 provider and attach the new session to the * existing user. If no matching user is found - the server will create a new * user. * * A user is limited to 10 active sessions at a time by default. [Learn more * about session limits](/docs/authentication#limits). * * * @param {OAuthProvider} provider * @param {string} success * @param {string} failure * @param {string[]} scopes * @throws {AppwriteException} * @returns {void|string} */ async createOAuth2Session(provider, success, failure, scopes) { const url = this._account.createOAuth2Session(provider, success, failure, scopes); return url; } /** * Update phone session * * Use this endpoint to create a session from token. * Provide the userId and secret parameters from the successful * response of authentication flows initiated by token creation. * For example, magic URL and phone login. * * @param {string} userId * @param {string} secret * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async updatePhoneSession(userId, secret) { const session = await this._account.updatePhoneSession(userId, secret); this.triggerAuthCheck(); return session; } /** * Create session * * Use this endpoint to create a session from token. * Provide the userId and secret parameters from the successful * response of authentication flows initiated by token creation. * For example, magic URL and phone login. * * @param {string} userId * @param {string} secret * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async createSession(userId, secret) { const session = await this._account.createSession(userId, secret); this.triggerAuthCheck(); return session; } /** * Get Session * * Use this endpoint to get a logged in user's session using a Session ID. * Inputting 'current' will return the current session being used. * * @param {string} sessionId * default is 'current' session * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async getSession(sessionId = 'current') { try { const session = await this._account.getSession(sessionId); return session; } catch (error) { throw new Error(`Could not retrieve Appwrite session for id: ${sessionId} `); } } /** * Update session * * Use this endpoint to extend a session's length. * Extending a session is useful when session expiry is short. * If the session was created using an OAuth provider, * this endpoint refreshes the access token from the provider. * * @param {string} sessionId * defaults to 'current' * @throws {AppwriteException} * @returns {Promise<Models.Session>} */ async updateSession(sessionId = 'current') { const result = await this._account.updateSession(sessionId); this.triggerAuthCheck(); return result; } /** * Delete Session * * Logout the user. Use 'current' as the session ID to logout on this device, * use a session ID to logout on another device. * If you're looking to logout the user on all devices, use Delete Sessions instead. * * * @param {string} sessionId * defaults to 'current' * @throws {AppwriteException} * @returns {Promise<{}>} */ async deleteSession(sessionId = 'current') { const result = await this._account.deleteSession(sessionId); this.triggerAuthCheck(); return result; } /** * Update status * * Block the currently logged in user account. Behind the scene, the user * record is not deleted but permanently blocked from any access. To * completely delete a user, use the Users API instead. * * @throws {AppwriteException} * @returns {Promise<Models.User<TPrefs>>} */ async updateStatus() { const account = await this._account.updateStatus(); this.triggerAuthCheck(); return account; } /** * Create push target * * No description at this moment * * @param {string} targetId * @param {string} identifier * @param {string} providerId * @throws {AppwriteException} * @returns {Promise<Models.Target>} */ async createPushTarget(targetId, identifier, providerId) { const account = await this._account.createPushTarget(targetId, identifier, providerId); this.triggerAuthCheck(); return account; } /** * Update push target * * No description at this moment * * @param {string} targetId * @param {string} identifier * @throws {AppwriteException} * @returns {Promise<Models.Target>} */ async updatePushTarget(targetId, identifier) { const account = await this._account.updatePushTarget(targetId, identifier); this.triggerAuthCheck(); return account; } /** * Delete push target * * No description at this moment * * @param {string} targetId * @throws {AppwriteException} * @returns {Promise<Models.Target>} */ async deletePushTarget(targetId) { const account = await this._account.deletePushTarget(targetId); this.triggerAuthCheck(); return account; } /** * Create email token (OTP) * * Sends the user an email with a secret key for creating a session. * If the provided user ID has not be registered, a new user will be created. * Use the returned user ID and secret and submit a request to the * POST /v1/account/sessions/token endpoint to complete the login process. * The secret sent to the user's email is valid for 15 minutes. * * A user is limited to 10 active sessions at a time by default. Learn more about session limits. * * @param {string} userId * @param {string} email * @param {boolean} phrase * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createEmailToken(userId, email, phrase = false) { const account = await this._account.createEmailToken(userId, email, phrase); this.triggerAuthCheck(); return account; } /** * Create magic URL token * * Sends the user an email with a secret key for creating a session. * If the provided user ID has not been registered, a new user will be created. * When the user clicks the link in the email, the user is redirected back to * the URL you provided with the secret key and userId values attached to * the URL query string. Use the query string parameters to submit a request * to the POST /v1/account/sessions/token endpoint to complete the login process. * * The link sent to the user's email address is valid for 1 hour. * If you are on a mobile device you can leave the URL parameter empty, * so that the login completion will be handled by your Appwrite instance * by default. * * A user is limited to 10 active sessions at a time by default. Learn more about session limits. * * @param {string} userId * @param {string} email * @param {string} url * Defaults to ID.unique() * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createMagicURLToken(email, url, userId = ID.unique(), phrase = true) { const session = await this._account.createMagicURLToken(userId, email, url, phrase); this.triggerAuthCheck(); return session; } /** * Create OAuth2 token * * Allow the user to login to their account using the OAuth2 provider of their choice. * Each OAuth2 provider should be enabled from the Appwrite console first. * Use the success and failure arguments to provide a redirect URL's back to * your app when login is completed. * * If authentication succeeds, userId and secret of a token will be appended to the success URL as query parameters. These can be used to create a new session using the Create session endpoint. * * A user is limited to 10 active sessions at a time by default. Learn more about session limits. * * * @param {OAuthProvider} provider * @param {string} success * @param {string} failure * @param {string[]} scopes * @throws {AppwriteException} * @returns {void|string} */ async createOAuth2Token(provider, success, failure, scopes) { const url = this._account.createOAuth2Token(provider, success, failure, scopes); return url; } /** * Create Phone token * * Sends the user an SMS with a secret key for creating a session. * If the provided user ID has not be registered, a new user will be created. * Use the returned user ID and secret and submit a request to the * POST /v1/account/sessions/token endpoint to complete the login process. * The secret sent to the user's phone is valid for 15 minutes. * * A user is limited to 10 active sessions at a time by default. Learn more about session limits. * * @param {string} userId * @param {string} phone * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createPhoneToken(userId, phone) { const session = await this._account.createPhoneToken(userId, phone); this.triggerAuthCheck(); return session; } /** * Create Email Verification * * Use this endpoint to send a verification message to your user email address * to confirm they are the valid owners of that address. Both the **userId** * and **secret** arguments will be passed as query parameters to the URL you * have provided to be attached to the verification email. The provided URL * should redirect the user back to your app and allow you to complete the * verification process by verifying both the **userId** and **secret** * parameters. Learn more about how to [complete the verification * process](/docs/client/account#accountUpdateEmailVerification). The * verification link sent to the user's email address is valid for 7 days. * * Please note that in order to avoid a [Redirect * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), * the only valid redirect URLs are the ones from domains you have set when * adding your platforms in the console interface. * * * @param {string} url * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createVerification(url) { const result = await this._account.createVerification(url); this.triggerAuthCheck(); return result; } /** * Create Email Verification (confirmation) * * Use this endpoint to complete the user email verification process. Use both * the **userId** and **secret** parameters that were attached to your app URL * to verify the user email ownership. If confirmed this route will return a * 200 status code. * * @param {string} userId * @param {string} secret * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async updateVerification(userId, secret) { const result = await this._account.updateVerification(userId, secret); this.triggerAuthCheck(); return result; } /** * Create Phone Verification * * Use this endpoint to send a verification SMS to the currently logged in * user. This endpoint is meant for use after updating a user's phone number * using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone) * endpoint. Learn more about how to [complete the verification * process](/docs/client/account#accountUpdatePhoneVerification). The * verification code sent to the user's phone number is valid for 15 minutes. * * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async createPhoneVerification() { const result = await this._account.createPhoneVerification(); this.triggerAuthCheck(); return result; } /** * Create Phone Verification (confirmation) * * Use this endpoint to complete the user phone verification process. Use the * **userId** and **secret** that were sent to your user's phone number to * verify the user email ownership. If confirmed this route will return a 200 * status code. * * @param {string} userId * @param {string} secret * @throws {AppwriteException} * @returns {Promise<Models.Token>} */ async updatePhoneVerification(userId, secret) { const result = await this._account.updatePhoneVerification(userId, secret); this.triggerAuthCheck(); return result; } /* -------------------------------------------------------------------------- */ /* Additional functionality */ /* -------------------------------------------------------------------------- */ /** * Convert Anonymous account with password * * This endpoint is a shortcut in order to convert an anonymous account * to a permanent one * * @param {string} email * @param {string} password * @param {ObjectSchema<TPrefs>} prefsSchema * @throws {AppwriteException} * @returns {Promise<Models.User<T>>} */ async convertAnonymousAccountWithEmailAndPassword(email, password) { const account = await this._account.updateEmail(email, password); this.triggerAuthCheck(); return account; } /** * Logout - Shortcut for deletesession * * @throws {AppwriteException} * @returns {Promise<{}> } */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type async logout() { return this.deleteSession('current'); } /** * Triggering an auth-check * * Trigger a check of all account and * session-related actions to enable * reactive monitoring of authentication status * @returns {void} */ triggerAuthCheck() { this._triggerManualAuthCheck$.next(true); } async _checkIfAuthExists() { try { const account = await this._account.get(); return account; } catch (error) { console.warn(error); return null; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Account, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Account, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Account, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); const DATABASE_ERROR = `No Database ID provided or database not initialized, use >>alternateDatabaseId << param`; class Databases { accountService = inject(Account); _databases = new Databases$1(CLIENT()); _client$ = of(CLIENT()).pipe(shareReplay(1)); /* -------------------------------------------------------------------------- */ /* Databases - CRUD - Appwrite API https://appwrite.io/docs/client/databases */ /* -------------------------------------------------------------------------- */ /** * Create Document * * Create a new Document. Before using this route, you should create a new * collection resource using either a [server * integration](/docs/server/databases#databasesCreateCollection) API or * directly from your database console. * * @param {string} collectionId * @param {Record<string, unknown>} data * @param {string[]} [permissions] * @param {string} [documentId] * defaults to ID.unique() * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise} */ async createDocument(collectionId, data, permissions, documentId = ID.unique(), alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); return this._databases.createDocument(databaseId, collectionId, documentId, this._cleanData(data), permissions); } } /** * Upsert Document * * Create a new Document if it can't be found (using $id), otherwise the document is updated. * * This will use incurr a read and a write to the database. * * Before using this route, you should create a new * collection resource using either a [server * integration](/docs/server/databases#databasesCreateCollection) API or * directly from your database console. * * @param {string} collectionId * @param {Record<string, unknown>} data * @param {string[]} [permissions] * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise} */ async upsertDocument(collectionId, data, permissions, alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); const id = data.$id ?? ID.unique(); try { // try to retrieve the document if it does exist update it const doc = await this.getDocument(collectionId, id); const merged = { ...doc, ...data }; return this.updateDocument(collectionId, id, merged); } catch (error) { return this.createDocument(collectionId, this._cleanData(data), permissions, ID.unique(), databaseId); } } } /** * Get Document * * Get a document by its unique ID. This endpoint response returns a JSON * object with the document data. * * @param {string} collectionId * @param {string} documentId * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise<DocumentShape>} */ async getDocument(collectionId, documentId, alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); return this._databases.getDocument(databaseId, collectionId, documentId); } } /** * List Documents * * Get a list of all the user's documents in a given collection. You can use * the query params to filter your results. * * @param {string} collectionId * @param {string[]} queries * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {PromisePromise<{total: number; documents: (Input<typeof AppwriteDocumentSchema> & Input<typeof validationSchema>)[]; }>} */ async listDocuments(collectionId, queries, alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); return this._databases.listDocuments(databaseId, collectionId, queries); } } /** * Update Document * * Update a document by its unique ID. Using the patch method you can pass * only specific fields that will get updated. * * @param {string} collectionId * @param {string} documentId * @param {unknown} data * @param {string[]} [permissions] * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise<DocumentShape>} */ async updateDocument(collectionId, documentId, data, permissions, alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); return this._databases.updateDocument(databaseId, collectionId, documentId, this._cleanData(data), permissions); } } /** * Delete Document * * Delete a document by its unique ID. * * @param {string} collectionId * @param {string} documentId * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise<void>} */ async deleteDocument(collectionId, documentId, alternateDatabaseId) { const databaseId = alternateDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error(DATABASE_ERROR); } else { this.accountService.triggerAuthCheck(); return this._databases.deleteDocument(databaseId, collectionId, documentId); } } /* -------------------------------------------------------------------------- */ /* Databases Realtime */ /* -------------------------------------------------------------------------- */ /* --------------- https://appwrite.io/docs/realtime#channels --------------- */ /* -------------------------------------------------------------------------- */ /** * Monitor Collection * * Monitors real-time changes in a collection. Uses the configured default database * An alternate database can be provided if needed * * @param {string} collectionId * @param {string[]} [queries] * @param {string | string[]} [events] * @param {string} [alternativeDatabaseId] * @throws {AppwriteException} * @returns {Observable<(Input<typeof AppwriteDocumentSchema> & Input<typeof validationSchema>)[]>} */ collection$(collectionId, queries = [], events, alternativeDatabaseId) { // check if required data is present runtime const { path } = this._generatePath(alternativeDatabaseId, collectionId); return this._client$.pipe(switchMap((client) => watch(client, path, events)), startWith(true), switchMap(() => { return this.listDocuments(collectionId, queries, alternativeDatabaseId); }), distinctUntilChanged((a, b) => deepEqual(a, b)), shareReplay(1)); } /** * Monitor Docuemnt * * Monitors real-time changes in a document. Uses the configured default database * An alternate database can be provided if needed * * @param {string} collectionId * @param {string} documentId * @param {string | string[]} [events] * @param {string} [alternativeDatabaseId] * @throws {AppwriteException} * @returns {Observable<(Input<typeof AppwriteDocumentSchema> & Input<typeof validationSchema>) | null>} */ document$(collectionId, documentId, events, alternativeDatabaseId) { return this.collection$(collectionId, [Query.equal('$id', documentId)], events, alternativeDatabaseId).pipe(map((res) => { if (res.documents[0]) { return res.documents[0]; } else { return null; } }), shareReplay(1)); } // TODO: query listening is done manually for now // watch this Issue // https://github.com/appwrite/appwrite/issues/2490 // https://appwrite.io/docs/databases#querying-documents // right now this is resolved by only watching ids of the original query list /* ----------------------------- Private Helpers ---------------------------- */ _generatePath(alternativeDatabaseId, collectionId) { const databaseId = alternativeDatabaseId ?? DEFAULT_DATABASE_ID(); if (!databaseId) { throw new Error('No Database ID provided or database not initialized, use alternateDatabaseId argument'); } // generate collection path const path = `databases.${databaseId}.collections.${collectionId}.documents`; return { path, databaseId }; } // remove the document meta data // eslint-disable-next-line @typescript-eslint/no-explicit-any _cleanData(data) { delete data.$collectionId; delete data.$permissions; delete data.$databaseId; delete data.$createdAt; delete data.$updatedAt; delete data.$id; return data; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Databases, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Databases, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: Databases, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class AppwriteAdapter { databases = inject(Databases); /** * Create Document * * Create a new Document. Before using this route, you should create a new * collection resource using either a [server * integration](/docs/server/databases#databasesCreateCollection) API or * directly from your database console. * @param {DocumentShape} awDocument * @param {string[]} [permissions] * @param {string} [alternativeDatabaseId] * @throws {AppwriteException} * @returns {Promise<DocumentShape>} */ async create(awDocument, permissions = [], documentId = ID.unique(), alternativeDatabaseId) { const data = await this.databases.createDocument(this.collectionId, awDocument, permissions, documentId, alternativeDatabaseId); if (this.validationFn) { return this.validationFn(data); } return data; } /** * Update Document * * Updates a Document. Before using this route, you should create a new * collection resource using either a [server * integration](/docs/server/databases#databasesCreateCollection) API or * directly from your database console. * @param {Partial<DocumentShape>} awDocument * @param {string[]} [permissions] * @param {string} [alternativeDatabaseId] * @throws {AppwriteException} * @returns {Promise<T & Models.Document>} */ async update(awDocument, permissions = [], alternativeDatabaseId) { if (!awDocument.$id) { throw new Error('Document must have an id'); } const data = await this.databases.updateDocument(this.collectionId, awDocument.$id, awDocument, permissions, alternativeDatabaseId); if (this.validationFn) { return this.validationFn(data); } return data; } /** * Upsert Document * * Upserts a Document. If an { $id: string } exists on the document, it is updated, otherwise a new document is created * Before using this route, you should create a new * collection resource using either a [server * integration](/docs/server/databases#databasesCreateCollection) API or * directly from your database console. * @param {DocumentShape} awDocument * @param {string[]} [permissions] * @param {string} [alternativeDatabaseId] * @throws {AppwriteException} * @returns {Promise<T & Models.Document>} */ async upsert(awDocument, permissions = [], alternativeDatabaseId) { const data = await this.databases.upsertDocument(this.collectionId, awDocument, permissions, alternativeDatabaseId); if (this.validationFn) { return this.validationFn(data); } return data; } /** * Delete Document * * Deletes a Document. * Takes either a document id or a document object with an id * @param {DocumentShape | string} awDocumentIdOrAwDocument * @param {string} [alternateDatabaseId] * @throws {AppwriteException} * @returns {Promise<T & Models.Document>} */ async delete(awDocumentIdOrAwDocument, alternateDatabaseId) { const id = typeof awDocumentIdOrAwDocument === 'string' ? awDocumentIdOrAwDocument : awDocumentIdOrAwDocument.$id; if (!id) { throw new Error('Document must have an id to be deleted'); } return this.databases.deleteDocument(this.collectionId, id, alternateDatabaseId); } async document(documentId, alternativeDatabaseId) { const data = await this.databases.getDocument(this.colle