pebblebed
Version:
Simplified interactions with Google Datastore for NodeJS
218 lines (183 loc) • 7.94 kB
text/typescript
import PebblebedModel from "../PebblebedModel";
import { DatastoreQueryRegular, TReturnOnly } from "../";
import { DatastoreQueryResponse, InternalDatastoreQuery, TFilterComparator } from "../types/PebblebedTypes";
import extractAncestorPaths from "../utility/extractAncestorPaths";
import augmentEntitiesWithIdProperties from "../utility/augmentEntitiesWithIdProperties";
import convertToType from "../utility/convertToType";
import Core, { UNSET_NAMESPACE } from "../Core";
import pickOutEntityFromResults from "../utility/pickOutEntityFromResults";
import { throwError, warn } from "../Messaging";
import deserializeJsonProperties from "../utility/deserializeJsonProperties";
const crypto = require("crypto");
export function createDatastoreQuery<T>(model: PebblebedModel, namespace: string|null): DatastoreQueryRegular<T> {
const idProp = model.entityIdProperty;
const kind = model.entityKind;
const hasIdProp = model.entityHasIdProperty;
const type = hasIdProp ? model.entitySchema[model.entityIdProperty!].type : null;
const schema = model.entitySchema;
const ns: string|null = namespace !== UNSET_NAMESPACE ? namespace : (Core.Instance.namespace !== UNSET_NAMESPACE ? Core.Instance.namespace : null);
const dsQuery =
(ns != null && ns !== UNSET_NAMESPACE)
? Core.Instance.dsModule.createQuery(ns, model.entityKind)
: Core.Instance.dsModule.createQuery(model.entityKind);
const runQuery = dsQuery.run.bind(dsQuery);
const filterQuery = dsQuery.filter.bind(dsQuery);
const useCache = (model.modelOptions.neverCache || !Core.Instance.caching)
? false
: Core.Instance.cacheDefaults.onQuery;
const returnOnlyEntity: TReturnOnly|null = null;
const cachingTimeSeconds =
model.modelOptions.defaultCachingSeconds != null
? model.modelOptions.defaultCachingSeconds
: Core.Instance.defaultCachingSeconds;
return Object.assign(
dsQuery,
{
returnOnlyEntity,
useCache,
cachingTimeSeconds,
enableCache(on: boolean) {
this.useCache = on;
return this;
},
cachingSeconds(seconds: number) {
this.cachingTimeSeconds = seconds;
return this;
},
first() {
this.returnOnlyEntity = "FIRST";
return this;
},
last() {
this.returnOnlyEntity = "LAST";
return this;
},
randomOne() {
this.returnOnlyEntity = "RANDOM";
return this;
},
filter(
property: string,
comparator: TFilterComparator,
value: string | number | boolean | Date
) {
if (!schema[property]) {
throwError(`Property "${property}" doesn't exist on entity schema for [ ${kind} ]`);
}
return filterQuery(property, comparator, convertToType(value, schema[property].type));
},
withAncestors(...args: any[]) {
const ancestors = extractAncestorPaths(model, ...args);
if (ns != null) {
this.hasAncestor(
Core.Instance.dsModule.key({
namespace: ns,
path: [].concat.apply([], ancestors),
})
);
} else {
this.hasAncestor(Core.Instance.dsModule.key([].concat.apply([], ancestors)));
}
return this;
},
async flushQueryInCache(): Promise<any> {
if (Core.Instance.cacheStore != null) {
const hash = createHashFromQuery(this);
await Core.Instance.cacheStore.flushQueryResponse(hash, this);
} else {
warn(`Trying to flush a query - but no Cache Store has been set on Pebblebed instance!`);
}
},
async run(throwIfNotFound: boolean = false) {
let hash: string|null = null;
if (Core.Instance.cacheStore != null && Core.Instance.cacheStore.cacheOnQuery && this.useCache) {
hash = createHashFromQuery(this);
const queryResponse: DatastoreQueryResponse<T> = await Core.Instance.cacheStore.getQueryResponse(
hash!,
this
);
if (queryResponse != null) {
cachingAugmentQueryEntitiesWithRealKeys(queryResponse);
deserializeJsonProperties(queryResponse.entities, schema);
if (this.returnOnlyEntity != null) {
return pickOutEntityFromResults(queryResponse.entities, this.returnOnlyEntity) as T;
}
return queryResponse;
}
}
const data = await runQuery();
if (hasIdProp && data[0].length > 0) {
augmentEntitiesWithIdProperties(data[0], idProp!, type!, kind);
}
const queryResponse = {
entities: data[0],
info: data[1],
};
if (
Core.Instance.cacheStore != null &&
Core.Instance.cacheStore.cacheOnQuery &&
this.useCache &&
queryResponse.entities.length > 0
) {
if (hash == null) {
hash = createHashFromQuery(this);
}
cachingAugmentQueryEntitiesWithSerializableKeys(queryResponse);
await Core.Instance.cacheStore.setQueryResponse(queryResponse, hash!, this.cachingTimeSeconds, this);
removeSerializableKeysFromEntities(queryResponse);
}
deserializeJsonProperties(queryResponse.entities, schema);
if (queryResponse.entities.length === 0 && throwIfNotFound) {
console.error(`Couldn't find any ${model.entityKind} entity(s) with specified query:\n\n${createDataStringFromQuery(this)}`);
throwError(`Couldn't find any ${model.entityKind} entity(s) with specified query, see server log for more detail`);
}
if (this.returnOnlyEntity != null) {
return pickOutEntityFromResults<T>(queryResponse.entities, this.returnOnlyEntity);
}
return queryResponse;
},
} as Partial<DatastoreQueryRegular<T>>
) as DatastoreQueryRegular<T>;
}
export function createDataStringFromQuery(query: InternalDatastoreQuery): string {
return `namespace:${query.namespace != null ? query.namespace : ""}
kinds:${query.kinds.join("-KIND_JOIN-")}
filters:${JSON.stringify(query.filters)}
limit:${query.limitVal}
offset:${query.offsetVal}
orders:${query.orders.join("-ORDERS_JOIN-")}
select:${query.selectVal.join("-SELECT_JOIN-")}
groupBy:${query.groupByVal.join("-GROUP_BY_JOIN-")}
start:${query.startVal}
end:${query.endVal}`;
}
export function createHashFromQuery(query: InternalDatastoreQuery) {
/*const dataString = `namespace:${query.namespace != null ? query.namespace : ""}
kinds:${query.kinds.join("-KIND_JOIN-")}
filters:${JSON.stringify(query.filters)}
limit:${query.limitVal}
offset:${query.offsetVal}
orders:${query.orders.join("-ORDERS_JOIN-")}
select:${query.selectVal.join("-SELECT_JOIN-")}
groupBy:${query.groupByVal.join("-GROUP_BY_JOIN-")}
start:${query.startVal}
end:${query.endVal}`;*/
return crypto.createHash("sha1").update(createDataStringFromQuery(query)).digest("base64");
}
const serializableKeyName = "__pebblebed_serializable_key__";
function cachingAugmentQueryEntitiesWithSerializableKeys(queryResponse: DatastoreQueryResponse<any>) {
for (const entity of queryResponse.entities) {
entity[serializableKeyName] = entity[Core.Instance.dsModule.KEY];
}
}
function removeSerializableKeysFromEntities(queryResponse: DatastoreQueryResponse<any>) {
for (const entity of queryResponse.entities) {
delete entity[serializableKeyName];
}
}
function cachingAugmentQueryEntitiesWithRealKeys(queryResponse: DatastoreQueryResponse<any>) {
for (const entity of queryResponse.entities) {
entity[Core.Instance.dsModule.KEY] = entity[serializableKeyName];
delete entity[serializableKeyName];
}
}