Skip to content

Commit

Permalink
Update store interface so limit is required on getByField(s) methods
Browse files Browse the repository at this point in the history
  • Loading branch information
stwiname committed Sep 29, 2024
1 parent 1002396 commit 8a5e01b
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 47 deletions.
10 changes: 5 additions & 5 deletions packages/cli/src/controller/codegen-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export async function generateJsonInterfaces(projectPath: string, schema: string
await renderTemplate(INTERFACE_TEMPLATE_PATH, path.join(typesDir, `interfaces.ts`), interfaceTemplate);
exportTypes.interfaces = true;
} catch (e) {
throw new Error(`When render json interfaces having problems.`);
throw new Error(`Codegen failed for json interface.`, {cause: e});
}
}
}
Expand All @@ -116,7 +116,7 @@ export async function generateEnums(projectPath: string, schema: string): Promis
await renderTemplate(ENUM_TEMPLATE_PATH, path.join(typesDir, `enums.ts`), enumsTemplate);
exportTypes.enums = true;
} catch (e) {
throw new Error(`When render enums having problems.`);
throw new Error(`Codegen failed for enums.`, {cause: e});
}
}
}
Expand Down Expand Up @@ -262,7 +262,7 @@ export async function codegen(projectPath: string, fileNames: string[] = [DEFAUL
},
});
} catch (e) {
throw new Error(`When render index in types having problems.`);
throw new Error(`Codegen failed for indexes.`, {cause: e});
}
console.log(`* Types index generated !`);
}
Expand Down Expand Up @@ -322,7 +322,7 @@ export async function generateModels(projectPath: string, schema: string): Promi
);
} catch (e) {
console.error(e);
throw new Error(`When render entity ${className} to schema having problems.`);
throw new Error(`Codegen failed for entity ${className}.`, {cause: e});
}
console.log(`* Schema ${className} generated !`);
}
Expand All @@ -339,7 +339,7 @@ export async function generateModels(projectPath: string, schema: string): Promi
});
exportTypes.models = true;
} catch (e) {
throw new Error(`When render index in models having problems.`);
throw new Error(`Failed to codgen for model indexes.`, {cause: e});
}
console.log(`* Models index generated !`);
}
Expand Down
40 changes: 22 additions & 18 deletions packages/cli/src/template/model.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
import {Entity, FunctionPropertyNames, FieldsExpression, GetOptions } from "@subql/types-core";
import assert from 'assert';
<%if (props.importJsonInterfaces.length !== 0) { %>
import {<% props.importJsonInterfaces.forEach(function(interface){ %>
import { <% props.importJsonInterfaces.forEach(function(interface){ %>
<%= interface %>,
<% }); %>} from '../interfaces';
<% } %>
<%if (props.importEnums.length !== 0) { %>
import {<% props.importEnums.forEach(function(e){ %>
import { <% props.importEnums.forEach(function(e){ %>
<%= e %>,
<% }); %>} from '../enums';<% } %>

export type <%= props.className %>Props = Omit<<%=props.className %>, NonNullable<FunctionPropertyNames<<%=props.className %>>>| '_name'>;
export type <%= props.className %>Props = Omit<<%=props.className %>, NonNullable<FunctionPropertyNames<<%=props.className %>>> | '_name'>;

export class <%= props.className %> implements Entity {

Expand All @@ -30,18 +30,18 @@ export class <%= props.className %> implements Entity {
return '<%=props.entityName %>';
}

async save(): Promise<void>{
async save(): Promise<void> {
let id = this.id;
assert(id !== null, "Cannot save <%=props.className %> entity without an ID");
await store.set('<%=props.entityName %>', id.toString(), this);
}

static async remove(id:string): Promise<void>{
static async remove(id: string): Promise<void> {
assert(id !== null, "Cannot remove <%=props.className %> entity without an ID");
await store.remove('<%=props.entityName %>', id.toString());
}

static async get(id:string): Promise<<%=props.className %> | undefined>{
static async get(id: string): Promise<<%=props.className %> | undefined> {
assert((id !== null && id !== undefined), "Cannot get <%=props.className %> entity without an ID");
const record = await store.get('<%=props.entityName %>', id.toString());
if (record) {
Expand All @@ -51,26 +51,30 @@ export class <%= props.className %> implements Entity {
}
}
<% props.indexedFields.forEach(function(field){ %>
static async getBy<%=helper.upperFirst(field.name) %>(<%=field.name %>: <%=field.type %>): Promise<<%=props.className %><%=field.unique ? '' : '[]' %> | undefined>{
<% if (field.unique) {%>
const record = await store.getOneByField('<%=props.entityName %>', '<%=field.name %>', <%=field.name %>);
if (record) {
return this.create(record as <%= props.className %>Props);
} else {
return;
}
<% } else { %>const records = await store.getByField('<%=props.entityName %>', '<%=field.name %>', <%=field.name %>);
return records.map(record => this.create(record as <%= props.className %>Props));<% }%>
<% if (field.unique) {%>
static async getBy<%=helper.upperFirst(field.name) %>(<%=field.name %>: <%=field.type %>): Promise<<%=props.className %> | undefined> {
const record = await store.getOneByField('<%=props.entityName %>', '<%=field.name %>', <%=field.name %>);
if (record) {
return this.create(record as <%= props.className %>Props);
} else {
return;
}
}
<% } else { %>static async getBy<%=helper.upperFirst(field.name) %>(<%=field.name %>: <%=field.type %>, options: GetOptions<<%=props.className %>>): Promise<<%=props.className %>[]> {
const records = await store.getByField<<%=props.className %>>('<%=props.entityName %>', '<%=field.name %>', <%=field.name %>, options);
return records.map(record => this.create(record as <%= props.className %>Props));
}
<% }%>
<% }); %>

/**
* Gets entities matching the specified filters and options.
*
* ⚠️ This function will first search cache data followed by DB data. Please consider this when using order and offset options.⚠️
* */
static async getByFields(filter: FieldsExpression<<%= props.className %>Props>[], options?: GetOptions<<%= props.className %>Props>): Promise<<%=props.className %>[]> {
const records = await store.getByFields('<%=props.entityName %>', filter, options);
static async getByFields(filter: FieldsExpression<<%= props.className %>Props>[], options: GetOptions<<%= props.className %>Props>): Promise<<%=props.className %>[]> {
const records = await store.getByFields<<%=props.className %>>('<%=props.entityName %>', filter, options);
return records.map(record => this.create(record as <%= props.className %>Props));
}

Expand Down
21 changes: 5 additions & 16 deletions packages/node-core/src/indexer/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
import assert from 'assert';
import {Store as IStore, Entity, FieldsExpression, GetOptions} from '@subql/types-core';
import {NodeConfig} from '../../configure';
import {getLogger} from '../../logger';
import {monitorWrite} from '../../process';
import {handledStringify} from '../../utils';
import {StoreCacheService} from '../storeCache';
import {StoreOperations} from '../StoreOperations';
import {OperationType} from '../types';
import {EntityClass} from './entity';

const logger = getLogger('Store');

/* A context is provided to allow it to be updated by the owner of the class instance */
type Context = {
blockHeight: number;
Expand All @@ -34,17 +31,9 @@ export class Store implements IStore {
this.#context = context;
}

#queryLimitCheck(storeMethod: string, entity: string, options?: GetOptions<any>) {
if (options) {
if (options.limit && this.#config.queryLimit < options.limit) {
logger.warn(
`store ${storeMethod} for entity ${entity} with ${options.limit} records exceeds config limit ${
this.#config.queryLimit
}. Will use ${this.#config.queryLimit} as the limit.`
);
}

options.limit = options.limit ? Math.min(options.limit, this.#config.queryLimit) : this.#config.queryLimit;
#queryLimitCheck(storeMethod: string, entity: string, options: GetOptions<any>) {
if (options.limit > this.#config.queryLimit) {
throw new Error(`Query limit exceeds the maximum allowed value of ${this.#config.queryLimit}`);
}
}

Expand All @@ -62,7 +51,7 @@ export class Store implements IStore {
entity: string,
field: keyof T,
value: T[keyof T] | T[keyof T][],
options: GetOptions<T> = {}
options: GetOptions<T>
): Promise<T[]> {
try {
const indexed = this.#context.isIndexed(entity, String(field));
Expand All @@ -81,7 +70,7 @@ export class Store implements IStore {
async getByFields<T extends Entity>(
entity: string,
filter: FieldsExpression<T>[],
options?: GetOptions<T>
options: GetOptions<T>
): Promise<T[]> {
try {
// Check that the fields are indexed
Expand Down
19 changes: 14 additions & 5 deletions packages/node-core/src/indexer/worker/worker.store.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import {Store, FieldsExpression, GetOptions} from '@subql/types-core';
import {Store, FieldsExpression, GetOptions, Entity} from '@subql/types-core';
import {unwrapProxyArgs} from './utils';

export type HostStore = {
// This matches the store interface
storeGet: (entity: string, id: string) => Promise<any | null>;
storeGetByField: (entity: string, field: string, value: any, options?: GetOptions<any>) => Promise<any[]>;
storeGetByFields: (entity: string, filter: FieldsExpression<any>[], options?: GetOptions<any>) => Promise<any[]>;
storeGetByField: <T extends Entity>(
entity: string,
field: keyof T,
value: any,
options: GetOptions<T>
) => Promise<T[]>;
storeGetByFields: <T extends Entity>(
entity: string,
filter: FieldsExpression<T>[],
options: GetOptions<T>
) => Promise<T[]>;
storeGetOneByField: (entity: string, field: string, value: any) => Promise<any | null>;
storeSet: (entity: string, id: string, data: any) => Promise<void>;
storeBulkCreate: (entity: string, data: any[]) => Promise<void>;
Expand Down Expand Up @@ -48,8 +57,8 @@ export const hostStoreToStore = (host: HostStore): Store => {
export function storeHostFunctions(store: Store): HostStore {
return {
storeGet: store.get.bind(store),
storeGetByField: store.getByField.bind(store),
storeGetByFields: store.getByFields.bind(store),
storeGetByField: store.getByField.bind<any>(store),
storeGetByFields: store.getByFields.bind<any>(store),
storeGetOneByField: store.getOneByField.bind(store),
storeSet: store.set.bind(store),
storeBulkCreate: store.bulkCreate.bind(store),
Expand Down
9 changes: 6 additions & 3 deletions packages/types-core/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export interface Entity {
}

export type GetOptions<T> = {
/**
* The number of items to return, if this exceeds the query-limit flag it will throw
* */
limit: number;
offset?: number;
limit?: number;
orderBy?: keyof T;
orderDirection?: 'ASC' | 'DESC';
};
Expand All @@ -33,11 +36,11 @@ export interface Store {
*
* ⚠️ This function will first search cache data followed by DB data. Please consider this when using order and offset options.⚠️
* */
getByFields<T extends Entity>(entity: string, filter: FieldsExpression<T>[], options?: GetOptions<T>): Promise<T[]>;
getByFields<T extends Entity>(entity: string, filter: FieldsExpression<T>[], options: GetOptions<T>): Promise<T[]>;
/**
* This is an alias for getByFields with a single filter
* */
getByField(entity: string, field: string, value: any, options?: GetOptions<Entity>): Promise<Entity[]>;
getByField<T extends Entity>(entity: string, field: keyof T, value: any, options: GetOptions<T>): Promise<T[]>;
/**
* This is an alias for getByField with limit set to 1
* */
Expand Down

0 comments on commit 8a5e01b

Please sign in to comment.