diff --git a/scripts/importData.bat b/scripts/importData.bat index 7e88f9f12e8fc6799a5429e6c30f413bfb01c012..2f3eb2efe7da13a17809a8b23c7cc38e4640037b 100644 --- a/scripts/importData.bat +++ b/scripts/importData.bat @@ -1,4 +1,4 @@ @echo off echo Please provide cozysessid (can be found after connection in browser dev tool) set /p token="CozySessid ? : " -ACH -t %token% import ./data/dayData.json +ACH -t %token% -u http://cozy.tools:8080 import ./data/dayData.json diff --git a/src/components/TotalConsumption/TotalConsumption.tsx b/src/components/TotalConsumption/TotalConsumption.tsx index cd64092a83fc4a76bcdaa9cb5b8796cff29e25d4..5d0991f610c815a23a98e0523039b3e7ef49cb41 100644 --- a/src/components/TotalConsumption/TotalConsumption.tsx +++ b/src/components/TotalConsumption/TotalConsumption.tsx @@ -37,9 +37,15 @@ const TotalConsumption: React.FC<TotalConsumptionProps> = ({ const converterService = new ConverterService() let total = 0 + let totalPrice = 0 actualData.forEach(data => { - if (data.value !== -1) total += data.value + if (data.value !== -1) { + total += data.value + } + if (data.price) { + totalPrice += converterService.LoadToEuro(data.price, fluidType) + } }) const displayedValue = @@ -50,9 +56,7 @@ const TotalConsumption: React.FC<TotalConsumptionProps> = ({ ? '-----' : fluidType === FluidType.MULTIFLUID ? formatNumberValues(total).toString() - : formatNumberValues( - converterService.LoadToEuro(total, fluidType) - ).toString() + : formatNumberValues(totalPrice).toString() setTotalValue(displayedValue) } diff --git a/src/models/dataload.model.ts b/src/models/dataload.model.ts index 22f7c95ceba53e99bc7c6cc9bd7bc410a83dd2fc..1297ae39ef1adf9328d1ed25923924659b9e9bc8 100644 --- a/src/models/dataload.model.ts +++ b/src/models/dataload.model.ts @@ -3,6 +3,7 @@ import { DateTime } from 'luxon' export interface Dataload { date: DateTime value: number + price: number valueDetail: number[] | null } diff --git a/src/models/indicator.model.ts b/src/models/indicator.model.ts index dbff5acc5560a035a2042f98a44e9ee7142cd7a1..35174f34242e15216a060f404041ded622e78e7a 100644 --- a/src/models/indicator.model.ts +++ b/src/models/indicator.model.ts @@ -2,4 +2,5 @@ export interface PerformanceIndicator { value: number | null compareValue: number | null percentageVariation: number | null + price?: number | null } diff --git a/src/services/consumption.service.ts b/src/services/consumption.service.ts index d8f492152983973b450a69074959f5d77847b989..de59c7024e9e110ea65cc239a1b4bd37b0f2d165 100644 --- a/src/services/consumption.service.ts +++ b/src/services/consumption.service.ts @@ -183,16 +183,22 @@ export default class ConsumptionDataManager { value: null, compareValue: null, percentageVariation: null, + price: null, } const actualDataIsValid = this.PerformanceIndicatorsDataIsValid( graphData.actualData ) - if (actualDataIsValid) + if (actualDataIsValid) { performanceIndicator.value = this.calculatePerformanceIndicatorValue( graphData.actualData ) + if (graphData.actualData[0].price) + performanceIndicator.price = this.calculatePerformanceIndicatorPrice( + graphData.actualData + ) + } if ( actualDataIsValid && @@ -229,6 +235,10 @@ export default class ConsumptionDataManager { return data.reduce((a, b) => (b.value !== -1 ? a + b.value : a), 0) } + public calculatePerformanceIndicatorPrice(data: Dataload[]): number { + return data.reduce((a, b) => (b.price !== null ? a + b.price : a), 0) + } + private calculatePerformanceIndicatorVariationPercentage( dataSum: number, comparisonDataSum: number @@ -435,7 +445,8 @@ export default class ConsumptionDataManager { else { convertedValue = converterService.LoadToEuro( value, - singleFluidChart.chartFluid + singleFluidChart.chartFluid, + singleFluidChart.chartData.actualData[i].price ) agreggatedConvertedValue += convertedValue } @@ -454,7 +465,8 @@ export default class ConsumptionDataManager { else { convertedComparisonValue = converterService.LoadToEuro( comparisonValue, - singleFluidChart.chartFluid + singleFluidChart.chartFluid, + singleFluidChart.chartData.comparisonData[i].price ) comparisonAgreggatedConvertedValue += convertedComparisonValue } @@ -525,17 +537,26 @@ export default class ConsumptionDataManager { return data.data } - //TODO: replace when merge /** * Get the first entry of a given data doctype (enedis, grdf, egl) * @param doctype * @returns */ - public async getFirsDataDateFromDoctype( + public async getFirstDataDateFromDoctypeWithPrice( doctype: Doctype ): Promise<DataloadEntity[] | null> { const query: QueryDefinition = Q(doctype) - .where({}) + .where({ + price: { + $exists: false, + }, + }) + .partialIndex({ + price: { + $exists: false, + }, + }) + .indexFields(['year', 'month']) .sortBy([{ year: 'asc' }, { month: 'asc' }]) .limitBy(1) const data = await this._client.query(query) @@ -561,15 +582,11 @@ export default class ConsumptionDataManager { * @param {DataloadEntity[]} consumptionDocs - Array of doc to save * @returns {DataloadEntity[]} Array of saved docs */ - public async saveDocs( - consumptionDocs: DataloadEntity[] - ): Promise<DataloadEntity[]> { - const savedDocs = consumptionDocs.map(async docToUpdate => { - const { - data: savedDoc, - }: QueryResult<DataloadEntity> = await this._client.save(docToUpdate) - return savedDoc - }) - return Promise.all(savedDocs) + public async saveDocs(consumptionDocs: DataloadEntity[]): Promise<boolean> { + const { + data: savedDocs, + }: QueryResult<DataloadEntity> = await this._client.saveAll(consumptionDocs) + + return true } } diff --git a/src/services/converter.service.ts b/src/services/converter.service.ts index 29519cbbaab475f96b5541b47d1404146f5057ee..92c1bdca2c3c1ccf3b4c468dd98a263742dddac5 100644 --- a/src/services/converter.service.ts +++ b/src/services/converter.service.ts @@ -9,13 +9,18 @@ export default class ConverterService { this._fluidConfig = new ConfigService().getFluidConfig() } - public LoadToEuro(load: number, fluidType: FluidType): number { + public LoadToEuro( + load: number, + fluidType: FluidType, + price?: number | null + ): number { let convertedLoad: number // If Multifluid do not apply coeff because it doesn't exist if (fluidType === FluidType.MULTIFLUID) { convertedLoad = load } else { - convertedLoad = load * this._fluidConfig[fluidType].coefficient + // convertedLoad = load * this._fluidConfig[fluidType].coefficient + convertedLoad = this.applyPrice(this._fluidConfig[fluidType], load, price) } // Prevent round 0 case when the actual value is not 0 @@ -24,4 +29,23 @@ export default class ConverterService { } return convertedLoad } + + /** + * Return calculated price + * @param {FluidConfig} fluidConfig - Fluid configuration + * @param {number} load - Load value + * @param {number} [price] - Price if exist + * @returns {number} + */ + private applyPrice( + fluidConfig: FluidConfig, + load: number, + price?: number | null + ): number { + if (price) { + return price + } else { + return load * fluidConfig.coefficient + } + } } diff --git a/src/services/performanceIndicator.service.ts b/src/services/performanceIndicator.service.ts index 59a0143c3b5eeac2605b7852f5ee008044662f9c..63ce8945685152aa9a9137fa2252b3819212fb1e 100644 --- a/src/services/performanceIndicator.service.ts +++ b/src/services/performanceIndicator.service.ts @@ -17,7 +17,11 @@ export default class PerformanceIndicatorService { const performanceIndicator = performanceIndicators[key] const fluidType = parseInt(key) currentValue += performanceIndicator.value - ? converterService.LoadToEuro(performanceIndicator.value, fluidType) + ? converterService.LoadToEuro( + performanceIndicator.value, + fluidType, + performanceIndicator ? performanceIndicator.price : null + ) : 0 } if ( @@ -33,7 +37,8 @@ export default class PerformanceIndicatorService { compareValue += performanceIndicator.compareValue ? converterService.LoadToEuro( performanceIndicator.compareValue, - fluidType + fluidType, + performanceIndicator ? performanceIndicator.price : null ) : 0 } @@ -47,7 +52,11 @@ export default class PerformanceIndicatorService { const performanceIndicator = performanceIndicators[key] const fluidType = parseInt(key) currentValue += performanceIndicator.value - ? converterService.LoadToEuro(performanceIndicator.value, fluidType) + ? converterService.LoadToEuro( + performanceIndicator.value, + fluidType, + performanceIndicator ? performanceIndicator.price : null + ) : 0 } } @@ -60,7 +69,8 @@ export default class PerformanceIndicatorService { compareValue += performanceIndicator.compareValue ? converterService.LoadToEuro( performanceIndicator.compareValue, - fluidType + fluidType, + performanceIndicator ? performanceIndicator.price : null ) : 0 } diff --git a/src/services/queryRunner.service.ts b/src/services/queryRunner.service.ts index aef650f2ef932e2274a06e19b7a4e39200d55208..f9733ef3d9057b040d7c3347483a23ffc9f866a2 100644 --- a/src/services/queryRunner.service.ts +++ b/src/services/queryRunner.service.ts @@ -146,6 +146,7 @@ export default class QueryRunner { keepLocalTime: true, }), value: entry.load, + price: entry.price, valueDetail: null, })) diff --git a/src/targets/services/fluidsPrices.ts b/src/targets/services/fluidsPrices.ts index 02cb8b5d0cb41aeda1b8b0406b0389ecb85e07f2..d5596a881d9d1e126418911aae520a66f962bf98 100644 --- a/src/targets/services/fluidsPrices.ts +++ b/src/targets/services/fluidsPrices.ts @@ -29,11 +29,11 @@ const getTimePeriod = async ( ): Promise<TimePeriod> => { switch (timeStep) { case TimeStep.HALF_AN_HOUR: - case TimeStep.DAY: return { startDate: date, endDate: date.plus({ day: 1 }).startOf('day'), } + case TimeStep.DAY: case TimeStep.MONTH: return { startDate: date.startOf('month'), @@ -128,12 +128,12 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { // Prices data exsit if (prices.length > 0) { log('debug', 'fluidPrices data found') - const firstMinuteData = await cdm.getFirsDataDateFromDoctype( + const firstMinuteData = await cdm.getFirstDataDateFromDoctypeWithPrice( getDoctypeTypeByFluid(fluidType) ) // If there is data, update hourly data and daily data - if (firstMinuteData) { + if (firstMinuteData && firstMinuteData.length > 0) { // Format first date const firstDate = DateTime.fromObject({ year: firstMinuteData[0].year, @@ -154,10 +154,10 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { fluidType, date ) - log( - 'debug', - `Step: ${timeStep} | Fluid : ${fluidType} | Date: ${date.day}/${date.month}/${date.year} | Price: ${priceData.price}` - ) + // log( + // 'debug', + // `Step: ${timeStep} | Fluid : ${fluidType} | Date: ${date.day}/${date.month}/${date.year} | Price: ${priceData.price}` + // ) const tp = await getTimePeriod(timeStep, date) // Get doc to update @@ -176,7 +176,11 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { } // Update date - date = date.plus({ days: 1 }) + if (timeStep === TimeStep.HALF_AN_HOUR) { + date = date.plus({ days: 1 }) + } else { + date = date.plus({ month: 1 }).startOf('month') + } } while (date < today) resolve() }) @@ -186,16 +190,17 @@ const applyPrices = async (client: Client, fluidType: FluidType) => { // Call aggregation method await aggregatePrices(client, qr, cdm, firstDate, today, fluidType) - } else log('info', 'No data found') + } else log('info', `No data found for fluid ${fluidType}`) } else log('info', 'No fluidesPrices data') } const processPrices = async ({ client }: PricesProps) => { log('info', `Processing electricity data...`) - await applyPrices(client, FluidType.ELECTRICITY) + const elec = applyPrices(client, FluidType.ELECTRICITY) log('info', `Electricity data done`) log('info', `Processing gas data...`) - await applyPrices(client, FluidType.GAS) + const gas = applyPrices(client, FluidType.GAS) + await Promise.all([elec, gas]) log('info', `Gas data done`) } diff --git a/src/types/cozy-client.d.ts b/src/types/cozy-client.d.ts index 37bdf2ed80350ff1488f1f097880203b683efd5e..b6186859507384142c042f203c65f2ad4276aedd 100644 --- a/src/types/cozy-client.d.ts +++ b/src/types/cozy-client.d.ts @@ -1,371 +1,383 @@ -import * as CozyStackClient from 'cozy-stack-client' -import { TRealtimePlugin, RealtimePlugin } from 'cozy-realtime' -import { TDoctype } from 'doctypes' -import { Relation } from 'models' - -declare module 'cozy-client' { - /** - * @typedef {object} HydratedDocument - */ - - export const CozyProvider: React.FC<{ client: Client; store?: any }> - - export function useClient(): Client - export function Q(doctype: TDoctype): QueryDefinition - - export type SortOptions = { [field: string]: 'asc' | 'desc' } - export type QueryDefinition = { - checkSortOrder(opts: { - sort: SortOptions - selector: unknown - indexedFields: unknown - }): QueryDefinition - getById(id: string): QueryDefinition - getByIds(ids: Array<string>): QueryDefinition - include(relations: Array<string>): QueryDefinition - indexFields(indexedFields: Array<string>): QueryDefinition - limitBy(limit: number): QueryDefinition - offset(skip: number): QueryDefinition - offsetBookmark(bookmark: string): QueryDefinition - offsetCursor(cursor): QueryDefinition - referencedBy(document: unknown): QueryDefinition - select(field: Array<string> | undefined): QueryDefinition - sortBy(sort: Array<SortOptions>): QueryDefinition - where(selector: MongoSelector): QueryDefinition - } - export type QueryResult<T, I = undefined> = { - bookmark: string - next: boolean - meta?: { count: number } - skip: number - data: T - included?: I - } - - export interface MongoSelector { - [field: unknown]: unknown - } - export interface AllQueryOptions { - bookmark?: string - keys?: unknown - limit?: number - skip?: number - } - export interface FindQueryOptions { - bookmark?: string - fields?: Array<string> - indexId?: string - limit?: number - skip?: number - sort?: SortOptions[] - } - interface ClientInstanceOpts { - cozyAppEditor: string - cozyAppName: string - cozyAppNamePrefix: string - cozyAppSlug: string - cozyDomain: string - cozyIconPath: string - cozyLocale: string - cozyToken: string - } - interface ClientToJSON { - uri: string - } - interface ClientLogin { - token: string - uri: string - } - interface ClientSchema { - byDoctype: TDoctype - client: Client - } - - export class Client { - appMetadata: { version: string; slug: string } - options: ClientLogin - idCounter: number - isLogged: boolean - instanceOptions: ClientInstanceOpts - links: unknown - chain: unknown - schema: unknown - plugins: { realtime: TRealtimePlugin; [key: string]: unknown } - - reducer(): any - setStore(store: any) - - /** - * A plugin is a class whose constructor receives the client as first argument. - * The main mean of interaction with the client should be with events - * like "login"/"logout". - * - * The plugin system is meant to encourage separation of concerns, modularity - * and testability : instead of registering events at module level, please - * create a plugin that subscribes to events. - * - * Plugin instances are stored internally in the `plugins` attribute of the client - * and can be accessed via this mean. A plugin class must have the attribute - * `pluginName` that will be use as the key in the `plugins` object. - * - * Two plugins with the same `pluginName` cannot co-exist. - * - * @example - * ``` - * class AlertPlugin { - * constructor(client, options) { - * this.client = client - * this.options = options - * this.handleLogin = this.handleLogin.bind(this) - * this.handleLogout = this.handleLogout.bind(this) - * this.client.on("login", this.handleLogin) - * this.client.on("logout", this.handleLogout) - * } - * - * handleLogin() { - * alert(this.options.onLoginAlert) - * } - * - * handleLogout() { - * alert(this.options.onLogoutAlert) - * } - * } - * - * AlertPlugin.pluginName = 'alerts' - * - * client.registerPlugin(AlertPlugin, { - * onLoginAlert: 'client has logged in !', - * onLogoutAlert: 'client has logged out !' - * }) - * - * // the instance of the plugin is accessible via - * client.plugins.alerts - * ``` - */ - registerPlugin(Plugin: RealtimePlugin | unknown, options?: unknown) - - /** - * Notify the links that they can start and set isLogged to true. - * - * On mobile, where url/token are set after instantiation, use this method - * to set the token and uri via options. - * - * Emits - * - * - "beforeLogin" at the beginning, before links have been set up - * - "login" when the client is fully logged in and links have been set up - * - * @param {object} options - Options - * @param {string} options.token - If passed, the token is set on the client - * @param {string} options.uri - If passed, the uri is set on the client - * @returns {Promise} - Resolves when all links have been setup and client is fully logged in - * - */ - login(options: ClientLogin): Promise<unknown> - - /** - * Logs out the client and reset all the links - * - * Emits - * - * - "beforeLogout" at the beginning, before links have been reset - * - "login" when the client is fully logged out and links have been reset - * - * @returns {Promise} - Resolves when all links have been reset and client is fully logged out - */ - logout(): Promise<unknown> - - /** - * Forwards to a stack client instance and returns - * a [DocumentCollection]{@link https://docs.cozy.io/en/cozy-client/api/cozy-stack-client/#DocumentCollection} instance. - * - * @param {string} doctype - The collection doctype. - * @returns {CozyStackClient.DocumentCollection} - Collection corresponding to the doctype - */ - collection(doctype: TDoctype): CozyStackClient.AllCollections - - /** - * Fetches an endpoint in an authorized way. - * - * @param {string} method The HTTP method. - * @param {string} path The URI. - * @param {object} body The payload. - * @param {object} opts Options for fetch - * @returns {Promise} - * @throws {FetchError} - */ - fetch( - method: string, - path: string, - body: unknown, - options?: unknown - ): Promise<unknown> - - find(doctype: string, selector?: MongoSelector): QueryDefinition - get(doctype: TDoctype, id: string): unknown - validate<D>(document: D): unknown - save<D, M = undefined>( - documentType: D, - mutationOptions?: M - ): Promise<QueryResult<T, I>> - - /** - * Creates a list of mutations to execute to create a document and its relationships. - * - * ```js - * const baseDoc = { _type: 'io.cozy.todo', label: 'Go hiking' } - * // relations can be arrays or single objects - * const relationships = { - * attachments: [{ _id: 12345, _type: 'io.cozy.files' }, { _id: 6789, _type: 'io.cozy.files' }], - * bills: { _id: 9999, _type: 'io.cozy.bills' } - * } - * client.getDocumentSavePlan(baseDoc, relationships) - * ``` - * - * @param {object} document The base document to create - * @param {object} relationships The list of relationships to add, as a dictionnary. Keys should be relationship names and values the documents to link. - * @returns {Mutation[]} One or more mutation to execute - */ - getDocumentSavePlan<D>(document: D, relationships: unknown) - triggerHook<D>(name: string, document: D): unknown - - /** - * Destroys a document. {before,after}:destroy hooks will be fired. - * - * @param {Document} document - Document to be deleted - * @returns {Document} The document that has been deleted - */ - destroy<D>(document: D): Promise<QueryResult<T, I>> - upload(file: File, dirPath: string, mutationOptions?: unknown) - ensureQueryExists( - queryId: string, - queryDefinition: QueryDefinition - ): unknown - - /** - * Executes a query and returns its results. - * - * Results from the query will be saved internally and can be retrieved via - * `getQueryFromState` or directly using `<Query />`. `<Query />` automatically - * executes its query when mounted if no fetch policy has been indicated. - * - * @param {QueryDefinition} queryDefinition - Definition that will be executed - * @param {string} options - Options - * @param {string} options.as - Names the query so it can be reused (by multiple components for example) - * @param {string} options.fetchPolicy - Fetch policy to bypass fetching based on what's already inside the state. See "Fetch policies" - * @returns {QueryResult} - */ - query( - queryDefinition: QueryDefinition, - { update, ...options }?: unknown - ): Promise<QueryResult<T, I>> - - /** - * Will fetch all documents for a `queryDefinition`, automatically fetching more - * documents if the total of documents is superior to the pagination limit. Can - * result in a lot of network requests. - * - * @param {QueryDefinition} queryDefinition - Definition to be executed - * @param {object} options - Options to the query - * @returns {Array} All documents matching the query - */ - queryAll( - queryDefinition: QueryDefinition, - options: object - ): Promise<QueryResult<T, I>> - makeObservableQuery( - queryDefinition: QueryDefinition, - options?: unknown - ): unknown - create<D>( - doctype: TDoctype, - entry: D, - relationships?: unknown, - options?: unknown - ): Promise<QueryResult<T, I>> - getStackClient(): ClientStackClient - getInstanceOptions(): ClientInstanceOpts - toJSON(): ClientToJSON - - /** - * Returns documents with their relationships resolved according to their schema. - * If related documents are not in the store, they will not be fetched automatically. - * Instead, the relationships will have null documents. - * - * @param {string} doctype - Doctype of the documents being hydrated - * @param {Array<Document>} documents - Documents to be hydrated - * @returns {Array<HydratedDocument>} - */ - hydrateDocuments<D>( - doctype: TDoctype, - documents: Array<D> - ): Array<HydratedDocument> - - /** - * Resolves relationships on a document. - * - * The original document is kept in the target attribute of - * the relationship - * - * @param {Document} document for which relationships must be resolved - * @param {Schema} schemaArg for the document doctype - * @returns {HydratedDocument} - */ - hydrateDocument<D>(document: D, schemaArg?: TDoctype): HydratedDocument - } - - class CCozyClient { - constructor(n: unknown): Client - } - const CozyClient: { - new (n: unknown): Client - } = CCozyClient - export default CozyClient - - export type HydratedDoc = any - - // FIX BEGIN :: Types temporary - export class HasMany { - /** - * Sets a relationship item with the relationship name and id - * - * @param {object} doc - Document to be updated - * @param {string} relName - Name of the relationship - * @param {string} relItemId - Id of the relationship item - * @param {object} relItemAttrs - Attributes to be set (at least _id and _type) - */ - static setHasManyItem<D, R>( - doc: D, - relName: string, - relItemId: string, - relItemAttrs: Relation - ): R - - /** - * Gets a relationship item with the relationship name and id - * - * @param {object} doc - Document to be updated - * @param {string} relName - Name of the relationship - * @param {string} relItemId - Id of the relationship item - */ - static getHasManyItem<D, R>(doc: D, relName: string, relItemId: string): R - - /** - * Updates a relationship item with the relationship name and id - * - * @param {object} doc - Document to be updated - * @param {string} relName - Name of the relationship - * @param {string} relItemId - Id of the relationship item - * @param {Function} updater - receives the current relationship item and should - * return an updated version. Merge should be used in the updater - * if previous relationship item fields are to be kept. - */ - static updateHasManyItem<D, R>( - doc: D, - relName: string, - relItemId: string, - updater: (relItem: Relation) => Relation - ): Promise<QueryResult<R>> - } - // FIX END -} +import * as CozyStackClient from 'cozy-stack-client' +import { TRealtimePlugin, RealtimePlugin } from 'cozy-realtime' +import { TDoctype } from 'doctypes' +import { Relation } from 'models' + +declare module 'cozy-client' { + /** + * @typedef {object} HydratedDocument + */ + + export const CozyProvider: React.FC<{ client: Client; store?: any }> + + export function useClient(): Client + export function Q(doctype: TDoctype): QueryDefinition + + export type SortOptions = { [field: string]: 'asc' | 'desc' } + export type QueryDefinition = { + checkSortOrder(opts: { + sort: SortOptions + selector: unknown + indexedFields: unknown + }): QueryDefinition + getById(id: string): QueryDefinition + getByIds(ids: Array<string>): QueryDefinition + include(relations: Array<string>): QueryDefinition + indexFields(indexedFields: Array<string>): QueryDefinition + partialIndex(partialFilter: object): QueryDefinition + limitBy(limit: number): QueryDefinition + offset(skip: number): QueryDefinition + offsetBookmark(bookmark: string): QueryDefinition + offsetCursor(cursor): QueryDefinition + referencedBy(document: unknown): QueryDefinition + select(field: Array<string> | undefined): QueryDefinition + sortBy(sort: Array<SortOptions>): QueryDefinition + where(selector: MongoSelector): QueryDefinition + } + export type QueryResult<T, I = undefined> = { + bookmark: string + next: boolean + meta?: { count: number } + skip: number + data: T + included?: I + } + + export interface MongoSelector { + [field: unknown]: unknown + } + export interface AllQueryOptions { + bookmark?: string + keys?: unknown + limit?: number + skip?: number + } + export interface FindQueryOptions { + bookmark?: string + fields?: Array<string> + indexId?: string + limit?: number + skip?: number + sort?: SortOptions[] + } + interface ClientInstanceOpts { + cozyAppEditor: string + cozyAppName: string + cozyAppNamePrefix: string + cozyAppSlug: string + cozyDomain: string + cozyIconPath: string + cozyLocale: string + cozyToken: string + } + interface ClientToJSON { + uri: string + } + interface ClientLogin { + token: string + uri: string + } + interface ClientSchema { + byDoctype: TDoctype + client: Client + } + + export class Client { + appMetadata: { version: string; slug: string } + options: ClientLogin + idCounter: number + isLogged: boolean + instanceOptions: ClientInstanceOpts + links: unknown + chain: unknown + schema: unknown + plugins: { realtime: TRealtimePlugin; [key: string]: unknown } + + reducer(): any + setStore(store: any) + + /** + * A plugin is a class whose constructor receives the client as first argument. + * The main mean of interaction with the client should be with events + * like "login"/"logout". + * + * The plugin system is meant to encourage separation of concerns, modularity + * and testability : instead of registering events at module level, please + * create a plugin that subscribes to events. + * + * Plugin instances are stored internally in the `plugins` attribute of the client + * and can be accessed via this mean. A plugin class must have the attribute + * `pluginName` that will be use as the key in the `plugins` object. + * + * Two plugins with the same `pluginName` cannot co-exist. + * + * @example + * ``` + * class AlertPlugin { + * constructor(client, options) { + * this.client = client + * this.options = options + * this.handleLogin = this.handleLogin.bind(this) + * this.handleLogout = this.handleLogout.bind(this) + * this.client.on("login", this.handleLogin) + * this.client.on("logout", this.handleLogout) + * } + * + * handleLogin() { + * alert(this.options.onLoginAlert) + * } + * + * handleLogout() { + * alert(this.options.onLogoutAlert) + * } + * } + * + * AlertPlugin.pluginName = 'alerts' + * + * client.registerPlugin(AlertPlugin, { + * onLoginAlert: 'client has logged in !', + * onLogoutAlert: 'client has logged out !' + * }) + * + * // the instance of the plugin is accessible via + * client.plugins.alerts + * ``` + */ + registerPlugin(Plugin: RealtimePlugin | unknown, options?: unknown) + + /** + * Notify the links that they can start and set isLogged to true. + * + * On mobile, where url/token are set after instantiation, use this method + * to set the token and uri via options. + * + * Emits + * + * - "beforeLogin" at the beginning, before links have been set up + * - "login" when the client is fully logged in and links have been set up + * + * @param {object} options - Options + * @param {string} options.token - If passed, the token is set on the client + * @param {string} options.uri - If passed, the uri is set on the client + * @returns {Promise} - Resolves when all links have been setup and client is fully logged in + * + */ + login(options: ClientLogin): Promise<unknown> + + /** + * Logs out the client and reset all the links + * + * Emits + * + * - "beforeLogout" at the beginning, before links have been reset + * - "login" when the client is fully logged out and links have been reset + * + * @returns {Promise} - Resolves when all links have been reset and client is fully logged out + */ + logout(): Promise<unknown> + + /** + * Forwards to a stack client instance and returns + * a [DocumentCollection]{@link https://docs.cozy.io/en/cozy-client/api/cozy-stack-client/#DocumentCollection} instance. + * + * @param {string} doctype - The collection doctype. + * @returns {CozyStackClient.DocumentCollection} - Collection corresponding to the doctype + */ + collection(doctype: TDoctype): CozyStackClient.AllCollections + + /** + * Fetches an endpoint in an authorized way. + * + * @param {string} method The HTTP method. + * @param {string} path The URI. + * @param {object} body The payload. + * @param {object} opts Options for fetch + * @returns {Promise} + * @throws {FetchError} + */ + fetch( + method: string, + path: string, + body: unknown, + options?: unknown + ): Promise<unknown> + + find(doctype: string, selector?: MongoSelector): QueryDefinition + get(doctype: TDoctype, id: string): unknown + validate<D>(document: D): unknown + save<D, M = undefined>( + documentType: D, + mutationOptions?: M + ): Promise<QueryResult<T, I>> + + /** + * Saves multiple documents in one batch + * - Can only be called with documents from the same doctype + * - Does not support automatic creation of references + * + * @param {CozyClientDocument[]} docs + * @param {Object} mutationOptions + * @returns {Promise<void>} + */ + saveAll(docs: D[], mutationOptions?: any): Promise<QueryResult<T, I>> + + /** + * Creates a list of mutations to execute to create a document and its relationships. + * + * ```js + * const baseDoc = { _type: 'io.cozy.todo', label: 'Go hiking' } + * // relations can be arrays or single objects + * const relationships = { + * attachments: [{ _id: 12345, _type: 'io.cozy.files' }, { _id: 6789, _type: 'io.cozy.files' }], + * bills: { _id: 9999, _type: 'io.cozy.bills' } + * } + * client.getDocumentSavePlan(baseDoc, relationships) + * ``` + * + * @param {object} document The base document to create + * @param {object} relationships The list of relationships to add, as a dictionnary. Keys should be relationship names and values the documents to link. + * @returns {Mutation[]} One or more mutation to execute + */ + getDocumentSavePlan<D>(document: D, relationships: unknown) + triggerHook<D>(name: string, document: D): unknown + + /** + * Destroys a document. {before,after}:destroy hooks will be fired. + * + * @param {Document} document - Document to be deleted + * @returns {Document} The document that has been deleted + */ + destroy<D>(document: D): Promise<QueryResult<T, I>> + upload(file: File, dirPath: string, mutationOptions?: unknown) + ensureQueryExists( + queryId: string, + queryDefinition: QueryDefinition + ): unknown + + /** + * Executes a query and returns its results. + * + * Results from the query will be saved internally and can be retrieved via + * `getQueryFromState` or directly using `<Query />`. `<Query />` automatically + * executes its query when mounted if no fetch policy has been indicated. + * + * @param {QueryDefinition} queryDefinition - Definition that will be executed + * @param {string} options - Options + * @param {string} options.as - Names the query so it can be reused (by multiple components for example) + * @param {string} options.fetchPolicy - Fetch policy to bypass fetching based on what's already inside the state. See "Fetch policies" + * @returns {QueryResult} + */ + query( + queryDefinition: QueryDefinition, + { update, ...options }?: unknown + ): Promise<QueryResult<T, I>> + + /** + * Will fetch all documents for a `queryDefinition`, automatically fetching more + * documents if the total of documents is superior to the pagination limit. Can + * result in a lot of network requests. + * + * @param {QueryDefinition} queryDefinition - Definition to be executed + * @param {object} options - Options to the query + * @returns {Array} All documents matching the query + */ + queryAll( + queryDefinition: QueryDefinition, + options: object + ): Promise<QueryResult<T, I>> + makeObservableQuery( + queryDefinition: QueryDefinition, + options?: unknown + ): unknown + create<D>( + doctype: TDoctype, + entry: D, + relationships?: unknown, + options?: unknown + ): Promise<QueryResult<T, I>> + getStackClient(): ClientStackClient + getInstanceOptions(): ClientInstanceOpts + toJSON(): ClientToJSON + + /** + * Returns documents with their relationships resolved according to their schema. + * If related documents are not in the store, they will not be fetched automatically. + * Instead, the relationships will have null documents. + * + * @param {string} doctype - Doctype of the documents being hydrated + * @param {Array<Document>} documents - Documents to be hydrated + * @returns {Array<HydratedDocument>} + */ + hydrateDocuments<D>( + doctype: TDoctype, + documents: Array<D> + ): Array<HydratedDocument> + + /** + * Resolves relationships on a document. + * + * The original document is kept in the target attribute of + * the relationship + * + * @param {Document} document for which relationships must be resolved + * @param {Schema} schemaArg for the document doctype + * @returns {HydratedDocument} + */ + hydrateDocument<D>(document: D, schemaArg?: TDoctype): HydratedDocument + } + + class CCozyClient { + constructor(n: unknown): Client + } + const CozyClient: { + new (n: unknown): Client + } = CCozyClient + export default CozyClient + + export type HydratedDoc = any + + // FIX BEGIN :: Types temporary + export class HasMany { + /** + * Sets a relationship item with the relationship name and id + * + * @param {object} doc - Document to be updated + * @param {string} relName - Name of the relationship + * @param {string} relItemId - Id of the relationship item + * @param {object} relItemAttrs - Attributes to be set (at least _id and _type) + */ + static setHasManyItem<D, R>( + doc: D, + relName: string, + relItemId: string, + relItemAttrs: Relation + ): R + + /** + * Gets a relationship item with the relationship name and id + * + * @param {object} doc - Document to be updated + * @param {string} relName - Name of the relationship + * @param {string} relItemId - Id of the relationship item + */ + static getHasManyItem<D, R>(doc: D, relName: string, relItemId: string): R + + /** + * Updates a relationship item with the relationship name and id + * + * @param {object} doc - Document to be updated + * @param {string} relName - Name of the relationship + * @param {string} relItemId - Id of the relationship item + * @param {Function} updater - receives the current relationship item and should + * return an updated version. Merge should be used in the updater + * if previous relationship item fields are to be kept. + */ + static updateHasManyItem<D, R>( + doc: D, + relName: string, + relItemId: string, + updater: (relItem: Relation) => Relation + ): Promise<QueryResult<R>> + } + // FIX END +}