Skip to content

Commit

Permalink
fix: constrain entity fields type to string-keyed object (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
wschurman authored Jun 4, 2024
1 parent 7c3c985 commit 7e2cea1
Show file tree
Hide file tree
Showing 23 changed files with 60 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export const DOES_NOT_EXIST_LOCAL_MEMORY_CACHE = Symbol('doesNotExist');
type LocalMemoryCacheValue<TFields> = Readonly<TFields> | typeof DOES_NOT_EXIST_LOCAL_MEMORY_CACHE;
export type LocalMemoryCache<TFields> = LRUCache<string, LocalMemoryCacheValue<TFields>>;

export default class GenericLocalMemoryCacher<TFields> implements IEntityGenericCacher<TFields> {
export default class GenericLocalMemoryCacher<TFields extends Record<string, any>>
implements IEntityGenericCacher<TFields>
{
constructor(
private readonly entityConfiguration: EntityConfiguration<TFields>,
private readonly localMemoryCache: LocalMemoryCache<TFields>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default class LocalMemoryCacheAdapterProvider implements IEntityCacheAdap
private readonly localMemoryCacheCreator: <TFields>() => LocalMemoryCache<TFields>
) {}

public getCacheAdapter<TFields>(
public getCacheAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): IEntityCacheAdapter<TFields> {
return computeIfAbsent(this.localMemoryCacheAdapterMap, entityConfiguration.tableName, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export interface GenericRedisCacheContext {
ttlSecondsNegative: number;
}

export default class GenericRedisCacher<TFields> implements IEntityGenericCacher<TFields> {
export default class GenericRedisCacher<TFields extends Record<string, any>>
implements IEntityGenericCacher<TFields>
{
constructor(
private readonly context: GenericRedisCacheContext,
private readonly entityConfiguration: EntityConfiguration<TFields>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import GenericRedisCacher, { GenericRedisCacheContext } from './GenericRedisCach
export default class RedisCacheAdapterProvider implements IEntityCacheAdapterProvider {
constructor(private readonly context: GenericRedisCacheContext) {}

getCacheAdapter<TFields>(
getCacheAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): IEntityCacheAdapter<TFields> {
return new GenericEntityCacheAdapter(new GenericRedisCacher(this.context, entityConfiguration));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { Knex } from 'knex';
import { JSONArrayField, MaybeJSONArrayField } from './EntityFields';
import wrapNativePostgresCallAsync from './errors/wrapNativePostgresCallAsync';

export default class PostgresEntityDatabaseAdapter<TFields> extends EntityDatabaseAdapter<TFields> {
export default class PostgresEntityDatabaseAdapter<
TFields extends Record<string, any>
> extends EntityDatabaseAdapter<TFields> {
protected getFieldTransformerMap(): FieldTransformerMap {
return new Map<string, FieldTransformer<any>>([
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import PostgresEntityDatabaseAdapter from './PostgresEntityDatabaseAdapter';
export default class PostgresEntityDatabaseAdapterProvider
implements IEntityDatabaseAdapterProvider
{
getDatabaseAdapter<TFields>(
getDatabaseAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): EntityDatabaseAdapter<TFields> {
return new PostgresEntityDatabaseAdapter(entityConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { v4 as uuidv4 } from 'uuid';
const dbObjects: Readonly<{ [key: string]: any }>[] = [];

export class InMemoryDatabaseAdapterProvider implements IEntityDatabaseAdapterProvider {
getDatabaseAdapter<TFields>(
getDatabaseAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): EntityDatabaseAdapter<TFields> {
return new InMemoryDatabaseAdapter(entityConfiguration);
Expand All @@ -26,7 +26,7 @@ export class InMemoryDatabaseAdapterProvider implements IEntityDatabaseAdapterPr
* In-memory database adapter for entity for the purposes of this example. Normally `@expo/entity-database-adapter-knex`
* or another production adapter would be used. Very similar to StubDatabaseAdapter but shared in a way more akin to a normal database.
*/
class InMemoryDatabaseAdapter<T> extends EntityDatabaseAdapter<T> {
class InMemoryDatabaseAdapter<T extends Record<string, any>> extends EntityDatabaseAdapter<T> {
protected getFieldTransformerMap(): FieldTransformerMap {
return new Map();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
* TLoadParams must be JSON stringifyable.
*/
export default class LocalMemorySecondaryEntityCache<
TFields,
TFields extends Record<string, any>,
TLoadParams
> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GenericRedisCacheContext, GenericRedisCacher } from '@expo/entity-cache
* A redis GenericSecondaryEntityCache.
*/
export default class RedisSecondaryEntityCache<
TFields,
TFields extends Record<string, any>,
TLoadParams
> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
constructor(
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/EntityCompanionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export default class EntityCompanionProvider {
return entityDatabaseAdapterFlavor.queryContextProvider;
}

private getTableDataCoordinatorForEntity<TFields>(
private getTableDataCoordinatorForEntity<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
entityClassName: string
): EntityTableDataCoordinator<TFields> {
Expand Down
4 changes: 2 additions & 2 deletions packages/entity/src/EntityConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { mapMap, invertMap, reduceMap } from './utils/collections/maps';
* The data storage configuration for a type of Entity. Contains information relating to IDs,
* cachable fields, field mappings, and types of cache and database adapter.
*/
export default class EntityConfiguration<TFields> {
export default class EntityConfiguration<TFields extends Record<string, any>> {
readonly idField: keyof TFields;
readonly tableName: string;
readonly cacheableKeys: ReadonlySet<keyof TFields>;
Expand Down Expand Up @@ -76,7 +76,7 @@ export default class EntityConfiguration<TFields> {
// external schema is a Record to typecheck that all fields have FieldDefinitions,
// but internally the most useful representation is a map for lookups
// TODO(wschurman): validate schema
this.schema = new Map(Object.entries(schema) as any);
this.schema = new Map(Object.entries(schema));

this.cacheableKeys = EntityConfiguration.computeCacheableKeys(this.schema);
this.entityToDBFieldsKeyMapping = EntityConfiguration.computeEntityToDBFieldsKeyMapping(
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/EntityDatabaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export interface TableQuerySelectionModifiersWithOrderByRaw extends TableQuerySe
* handles all entity field transformation. Subclasses are responsible for
* implementing database-specific logic for a type of database.
*/
export default abstract class EntityDatabaseAdapter<TFields> {
export default abstract class EntityDatabaseAdapter<TFields extends Record<string, any>> {
private readonly fieldTransformerMap: FieldTransformerMap;

constructor(private readonly entityConfiguration: EntityConfiguration<TFields>) {
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/IEntityCacheAdapterProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default interface IEntityCacheAdapterProvider {
/**
* Vend a cache adapter for an entity configuration.
*/
getCacheAdapter<TFields>(
getCacheAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): IEntityCacheAdapter<TFields>;
}
2 changes: 1 addition & 1 deletion packages/entity/src/IEntityDatabaseAdapterProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default interface IEntityDatabaseAdapterProvider {
/**
* Vend a database adapter.
*/
getDatabaseAdapter<TFields>(
getDatabaseAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): EntityDatabaseAdapter<TFields>;
}
4 changes: 3 additions & 1 deletion packages/entity/src/__tests__/ComposedCacheAdapter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const entityConfiguration = new EntityConfiguration<BlahFields>({
export const DOES_NOT_EXIST_LOCAL_MEMORY_CACHE = Symbol('doesNotExist');
type LocalMemoryCacheValue<TFields> = Readonly<TFields> | typeof DOES_NOT_EXIST_LOCAL_MEMORY_CACHE;

class TestLocalCacheAdapter<TFields> implements IEntityCacheAdapter<TFields> {
class TestLocalCacheAdapter<TFields extends Record<string, any>>
implements IEntityCacheAdapter<TFields>
{
constructor(
private readonly entityConfiguration: EntityConfiguration<TFields>,
private readonly cache: Map<string, LocalMemoryCacheValue<TFields>>
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/__tests__/EntityMutator-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ const createEntityMutatorFactory = (
)
);
const customStubDatabaseAdapterProvider: IEntityDatabaseAdapterProvider = {
getDatabaseAdapter<TFields>(
getDatabaseAdapter<TFields extends Record<string, any>>(
_entityConfiguration: EntityConfiguration<TFields>
): EntityDatabaseAdapter<TFields> {
return databaseAdapter as any as EntityDatabaseAdapter<TFields>;
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/internal/EntityDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { computeIfAbsent, zipToMap } from '../utils/collections/maps';
*
* It is also responsible for invalidating all sources of data when mutated using EntityMutator.
*/
export default class EntityDataManager<TFields> {
export default class EntityDataManager<TFields extends Record<string, any>> {
private readonly fieldDataLoaders: Map<
keyof TFields,
DataLoader<NonNullable<TFields[keyof TFields]>, readonly Readonly<TFields>[]>
Expand Down
30 changes: 21 additions & 9 deletions packages/entity/src/internal/EntityFieldTransformationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface FieldTransformer<T> {
*/
export type FieldTransformerMap = Map<string, FieldTransformer<any>>;

export const getDatabaseFieldForEntityField = <TFields>(
export const getDatabaseFieldForEntityField = <TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
entityField: keyof TFields
): string => {
Expand All @@ -29,7 +29,7 @@ export const getDatabaseFieldForEntityField = <TFields>(
return databaseField!;
};

export const transformDatabaseObjectToFields = <TFields>(
export const transformDatabaseObjectToFields = <TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
databaseObject: { [key: string]: any }
Expand All @@ -50,7 +50,7 @@ export const transformDatabaseObjectToFields = <TFields>(
return fields;
};

export const transformFieldsToDatabaseObject = <TFields>(
export const transformFieldsToDatabaseObject = <TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fields: Readonly<Partial<TFields>>
Expand All @@ -70,7 +70,7 @@ export const transformFieldsToDatabaseObject = <TFields>(
return databaseObject;
};

export const transformCacheObjectToFields = <TFields>(
export const transformCacheObjectToFields = <TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
cacheObject: { [key: string]: any }
Expand All @@ -88,7 +88,7 @@ export const transformCacheObjectToFields = <TFields>(
return fields;
};

export const transformFieldsToCacheObject = <TFields>(
export const transformFieldsToCacheObject = <TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fields: Readonly<Partial<TFields>>
Expand All @@ -106,7 +106,10 @@ export const transformFieldsToCacheObject = <TFields>(
return cacheObject;
};

const maybeTransformDatabaseValueToFieldValue = <TFields, N extends keyof TFields>(
const maybeTransformDatabaseValueToFieldValue = <
TFields extends Record<string, any>,
N extends keyof TFields
>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fieldName: N,
Expand All @@ -120,7 +123,10 @@ const maybeTransformDatabaseValueToFieldValue = <TFields, N extends keyof TField
return readTransformer ? readTransformer(value) : value;
};

const maybeTransformFieldValueToDatabaseValue = <TFields, N extends keyof TFields>(
const maybeTransformFieldValueToDatabaseValue = <
TFields extends Record<string, any>,
N extends keyof TFields
>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fieldName: N,
Expand All @@ -132,7 +138,10 @@ const maybeTransformFieldValueToDatabaseValue = <TFields, N extends keyof TField
return writeTransformer ? writeTransformer(value) : value;
};

const maybeTransformCacheValueToFieldValue = <TFields, N extends keyof TFields>(
const maybeTransformCacheValueToFieldValue = <
TFields extends Record<string, any>,
N extends keyof TFields
>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fieldName: N,
Expand All @@ -148,7 +157,10 @@ const maybeTransformCacheValueToFieldValue = <TFields, N extends keyof TFields>(
return readTransformer ? readTransformer(value) : value;
};

const maybeTransformFieldValueToCacheValue = <TFields, N extends keyof TFields>(
const maybeTransformFieldValueToCacheValue = <
TFields extends Record<string, any>,
N extends keyof TFields
>(
entityConfiguration: EntityConfiguration<TFields>,
fieldTransformerMap: FieldTransformerMap,
fieldName: N,
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/internal/EntityTableDataCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import IEntityMetricsAdapter from '../metrics/IEntityMetricsAdapter';
* table. Note that one instance is shared amongst all entities that read from
* the table to ensure cross-entity data consistency.
*/
export default class EntityTableDataCoordinator<TFields> {
export default class EntityTableDataCoordinator<TFields extends Record<string, any>> {
readonly databaseAdapter: EntityDatabaseAdapter<TFields>;
readonly cacheAdapter: IEntityCacheAdapter<TFields>;
readonly dataManager: EntityDataManager<TFields>;
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/internal/ReadThroughEntityCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type CacheLoadResult<TFields> =
* A read-through entity cache is responsible for coordinating EntityDatabaseAdapter and
* EntityCacheAdapter within the EntityDataManager.
*/
export default class ReadThroughEntityCache<TFields> {
export default class ReadThroughEntityCache<TFields extends Record<string, any>> {
constructor(
private readonly entityConfiguration: EntityConfiguration<TFields>,
private readonly entityCacheAdapter: IEntityCacheAdapter<TFields>
Expand Down
8 changes: 5 additions & 3 deletions packages/entity/src/utils/testing/StubCacheAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import IEntityCacheAdapterProvider from '../../IEntityCacheAdapterProvider';
import { CacheStatus, CacheLoadResult } from '../../internal/ReadThroughEntityCache';

export class NoCacheStubCacheAdapterProvider implements IEntityCacheAdapterProvider {
getCacheAdapter<TFields>(
getCacheAdapter<TFields extends Record<string, any>>(
_entityConfiguration: EntityConfiguration<TFields>
): IEntityCacheAdapter<TFields> {
return new NoCacheStubCacheAdapter();
Expand Down Expand Up @@ -45,7 +45,7 @@ export class NoCacheStubCacheAdapter<TFields> implements IEntityCacheAdapter<TFi
export class InMemoryFullCacheStubCacheAdapterProvider implements IEntityCacheAdapterProvider {
cache: Map<string, Readonly<object>> = new Map();

getCacheAdapter<TFields>(
getCacheAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): IEntityCacheAdapter<TFields> {
return new InMemoryFullCacheStubCacheAdapter(
Expand All @@ -55,7 +55,9 @@ export class InMemoryFullCacheStubCacheAdapterProvider implements IEntityCacheAd
}
}

export class InMemoryFullCacheStubCacheAdapter<TFields> implements IEntityCacheAdapter<TFields> {
export class InMemoryFullCacheStubCacheAdapter<TFields extends Record<string, any>>
implements IEntityCacheAdapter<TFields>
{
constructor(
private readonly entityConfiguration: EntityConfiguration<TFields>,
readonly cache: Map<string, Readonly<TFields>>
Expand Down
6 changes: 4 additions & 2 deletions packages/entity/src/utils/testing/StubDatabaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import {
} from '../../internal/EntityFieldTransformationUtils';
import { computeIfAbsent, mapMap } from '../collections/maps';

export default class StubDatabaseAdapter<T> extends EntityDatabaseAdapter<T> {
export default class StubDatabaseAdapter<
T extends Record<string, any>
> extends EntityDatabaseAdapter<T> {
constructor(
private readonly entityConfiguration2: EntityConfiguration<T>,
private readonly dataStore: Map<string, Readonly<{ [key: string]: any }>[]>
) {
super(entityConfiguration2);
}

public static convertFieldObjectsToDataStore<T>(
public static convertFieldObjectsToDataStore<T extends Record<string, any>>(
entityConfiguration: EntityConfiguration<T>,
dataStore: Map<string, Readonly<T>[]>
): Map<string, Readonly<{ [key: string]: any }>[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import IEntityDatabaseAdapterProvider from '../../IEntityDatabaseAdapterProvider
export default class StubDatabaseAdapterProvider implements IEntityDatabaseAdapterProvider {
private readonly objectCollection = new Map();

getDatabaseAdapter<TFields>(
getDatabaseAdapter<TFields extends Record<string, any>>(
entityConfiguration: EntityConfiguration<TFields>
): EntityDatabaseAdapter<TFields> {
return new StubDatabaseAdapter(entityConfiguration, this.objectCollection);
Expand Down

0 comments on commit 7e2cea1

Please sign in to comment.