Skip to content

Commit

Permalink
feat(@aws-amplify/datastore): hasOne CRUD improvements (#9239)
Browse files Browse the repository at this point in the history
  • Loading branch information
iartemiev authored Nov 17, 2021
1 parent c316b3e commit d521d17
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 76 deletions.
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ module.exports = {
trailingComma: 'es5',
singleQuote: true,
useTabs: true,
arrowParens: 'avoid',
};
Original file line number Diff line number Diff line change
Expand Up @@ -194,21 +194,19 @@ export class SQLiteAdapter implements StorageAdapter {
switch (relationType) {
case 'HAS_ONE':
for await (const recordItem of records) {
if (recordItem[fieldName]) {
const [queryStatement, params] = queryByIdStatement(
recordItem[fieldName],
tableName
);
const getByfield = recordItem[targetName] ? targetName : fieldName;
if (!recordItem[getByfield]) break;

const connectionRecord = await this.db.get(
queryStatement,
params
);
const [queryStatement, params] = queryByIdStatement(
recordItem[getByfield],
tableName
);

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}
const connectionRecord = await this.db.get(queryStatement, params);

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}

break;
Expand Down Expand Up @@ -236,7 +234,7 @@ export class SQLiteAdapter implements StorageAdapter {
// TODO: Lazy loading
break;
default:
const _: never = relationType;
const _: never = relationType as never;
throw new Error(`invalid relation type ${relationType}`);
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,8 @@ export const implicitAuthFieldsForModel: (model: SchemaModel) => string[] = (
return [];
}

const authRules: ModelAttributeAuth = model.attributes.find(
isModelAttributeAuth
);
const authRules: ModelAttributeAuth =
model.attributes.find(isModelAttributeAuth);

if (!authRules) {
return [];
Expand Down Expand Up @@ -307,7 +306,7 @@ export function whereClauseFromPredicate<T extends PersistentModel>(
filterType = 'OR';
break;
default:
const _: never = groupType;
const _: never = groupType as never;
throw new Error(`Invalid ${groupType}`);
}

Expand All @@ -319,9 +318,8 @@ export function whereClauseFromPredicate<T extends PersistentModel>(
`${isNegation ? 'NOT' : ''}(${groupResult.join(` ${filterType} `)})`
);
} else if (isPredicateObj(predicate)) {
const [condition, conditionParams] = whereConditionFromPredicateObject(
predicate
);
const [condition, conditionParams] =
whereConditionFromPredicateObject(predicate);

result.push(condition);
params.push(...conditionParams);
Expand Down
4 changes: 2 additions & 2 deletions packages/datastore-storage-adapter/src/SQLiteAdapter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function getSQLiteType(
case 'Float':
return 'REAL';
default:
const _: never = scalar;
throw new Error(`unknown type ${scalar}`);
const _: never = scalar as never;
throw new Error(`unknown type ${scalar as string}`);
}
}
54 changes: 50 additions & 4 deletions packages/datastore/__tests__/AsyncStorageAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ let DataStore: typeof DataStoreType;
const ASAdapter = <any>AsyncStorageAdapter;

describe('AsyncStorageAdapter tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('Query', () => {
let Model: PersistentModelConstructor<Model>;
let model1Id: string;
Expand Down Expand Up @@ -56,10 +60,6 @@ describe('AsyncStorageAdapter tests', () => {
);
});

beforeEach(() => {
jest.clearAllMocks();
});

it('Should call getById for query by id', async () => {
const result = await DataStore.query(Model, model1Id);

Expand Down Expand Up @@ -169,4 +169,50 @@ describe('AsyncStorageAdapter tests', () => {
expect(profile).toBeUndefined;
});
});

describe('Save', () => {
let User: PersistentModelConstructor<User>;
let Profile: PersistentModelConstructor<Profile>;
let profile: Profile;

beforeAll(async () => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));

const classes = initSchema(testSchema());

({ User } = classes as {
User: PersistentModelConstructor<User>;
});

({ Profile } = classes as {
Profile: PersistentModelConstructor<Profile>;
});

profile = await DataStore.save(
new Profile({ firstName: 'Rick', lastName: 'Bob' })
);
});

it('should allow linking model via model field', async () => {
const savedUser = await DataStore.save(
new User({ name: 'test', profile })
);
const user1Id = savedUser.id;

const user = await DataStore.query(User, user1Id);
expect(user.profileID).toEqual(profile.id);
expect(user.profile).toEqual(profile);
});

it('should allow linking model via FK', async () => {
const savedUser = await DataStore.save(
new User({ name: 'test', profileID: profile.id })
);
const user1Id = savedUser.id;

const user = await DataStore.query(User, user1Id);
expect(user.profileID).toEqual(profile.id);
expect(user.profile).toEqual(profile);
});
});
});
46 changes: 46 additions & 0 deletions packages/datastore/__tests__/IndexedDBAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,50 @@ describe('IndexedDBAdapter tests', () => {
expect(profile).toBeUndefined;
});
});

describe('Save', () => {
let User: PersistentModelConstructor<User>;
let Profile: PersistentModelConstructor<Profile>;
let profile: Profile;

beforeAll(async () => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));

const classes = initSchema(testSchema());

({ User } = classes as {
User: PersistentModelConstructor<User>;
});

({ Profile } = classes as {
Profile: PersistentModelConstructor<Profile>;
});

profile = await DataStore.save(
new Profile({ firstName: 'Rick', lastName: 'Bob' })
);
});

it('should allow linking model via model field', async () => {
const savedUser = await DataStore.save(
new User({ name: 'test', profile })
);
const user1Id = savedUser.id;

const user = await DataStore.query(User, user1Id);
expect(user.profileID).toEqual(profile.id);
expect(user.profile).toEqual(profile);
});

it('should allow linking model via FK', async () => {
const savedUser = await DataStore.save(
new User({ name: 'test', profileID: profile.id })
);
const user1Id = savedUser.id;

const user = await DataStore.query(User, user1Id);
expect(user.profileID).toEqual(profile.id);
expect(user.profile).toEqual(profile);
});
});
});
3 changes: 2 additions & 1 deletion packages/datastore/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export declare class Comment {
export declare class User {
public readonly id: string;
public readonly name: string;
public readonly profileID: string;
public readonly profile?: Profile;
public readonly profileID?: string;
}
export declare class Profile {
public readonly id: string;
Expand Down
38 changes: 20 additions & 18 deletions packages/datastore/src/storage/adapter/AsyncStorageAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,17 @@ export class AsyncStorageAdapter implements Adapter {
switch (relationType) {
case 'HAS_ONE':
for await (const recordItem of records) {
if (recordItem[fieldName]) {
const connectionRecord = await this.db.get(
recordItem[fieldName],
storeName
);
const getByfield = recordItem[targetName] ? targetName : fieldName;
if (!recordItem[getByfield]) break;

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}
const connectionRecord = await this.db.get(
recordItem[getByfield],
storeName
);

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}

break;
Expand Down Expand Up @@ -352,9 +353,9 @@ export class AsyncStorageAdapter implements Adapter {
// models to be deleted.
const models = await this.query(modelConstructor, condition);
// TODO: refactor this to use a function like getRelations()
const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;

if (condition !== undefined) {
await this.deleteTraverse(
Expand Down Expand Up @@ -419,9 +420,9 @@ export class AsyncStorageAdapter implements Adapter {
throw new Error(msg);
}

const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;
await this.deleteTraverse(
relations,
[model],
Expand All @@ -430,9 +431,9 @@ export class AsyncStorageAdapter implements Adapter {
deleteQueue
);
} else {
const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;

await this.deleteTraverse(
relations,
Expand Down Expand Up @@ -510,6 +511,7 @@ export class AsyncStorageAdapter implements Adapter {

const hasOneCustomField = targetName in model;
const value = hasOneCustomField ? model[targetName] : model.id;
if (!value) break;

const allRecords = await this.db.getAll(storeName);
const recordToDelete = allRecords.filter(
Expand Down
58 changes: 32 additions & 26 deletions packages/datastore/src/storage/adapter/IndexedDBAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ class IndexedDBAdapter implements Adapter {
autoIncrement: true,
});

const indexes = this.schema.namespaces[namespaceName]
.relationships[modelName].indexes;
const indexes =
this.schema.namespaces[namespaceName].relationships[
modelName
].indexes;
indexes.forEach(index => store.createIndex(index, index));

store.createIndex('byId', 'id', { unique: true });
Expand Down Expand Up @@ -310,16 +312,17 @@ class IndexedDBAdapter implements Adapter {
switch (relation.relationType) {
case 'HAS_ONE':
for await (const recordItem of records) {
if (recordItem[fieldName]) {
const connectionRecord = await this._get(
store,
recordItem[fieldName]
);
const getByfield = recordItem[targetName] ? targetName : fieldName;
if (!recordItem[getByfield]) break;

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}
const connectionRecord = await this._get(
store,
recordItem[getByfield]
);

recordItem[fieldName] =
connectionRecord &&
this.modelInstanceCreator(modelConstructor, connectionRecord);
}

break;
Expand Down Expand Up @@ -533,9 +536,9 @@ class IndexedDBAdapter implements Adapter {
const storeName = this.getStorenameForModel(modelConstructor);

const models = await this.query(modelConstructor, condition);
const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;

if (condition !== undefined) {
await this.deleteTraverse(
Expand Down Expand Up @@ -611,9 +614,9 @@ class IndexedDBAdapter implements Adapter {
}
await tx.done;

const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;

await this.deleteTraverse(
relations,
Expand All @@ -623,9 +626,9 @@ class IndexedDBAdapter implements Adapter {
deleteQueue
);
} else {
const relations = this.schema.namespaces[nameSpace].relationships[
modelConstructor.name
].relationTypes;
const relations =
this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
.relationTypes;

await this.deleteTraverse(
relations,
Expand Down Expand Up @@ -709,12 +712,15 @@ class IndexedDBAdapter implements Adapter {

const hasOneCustomField = targetName in model;
const value = hasOneCustomField ? model[targetName] : model.id;

const recordToDelete = <T>await this.db
.transaction(storeName, 'readwrite')
.objectStore(storeName)
.index(hasOneIndex)
.get(value);
if (!value) break;

const recordToDelete = <T>(
await this.db
.transaction(storeName, 'readwrite')
.objectStore(storeName)
.index(hasOneIndex)
.get(value)
);

await this.deleteTraverse(
this.schema.namespaces[nameSpace].relationships[modelName]
Expand Down
Loading

0 comments on commit d521d17

Please sign in to comment.