Skip to content

Commit

Permalink
fix: DeepPickFromPath<> to flatten Arrays of Models referenced in a j…
Browse files Browse the repository at this point in the history
…oin table (#417)
  • Loading branch information
zxl629 authored Dec 19, 2024
1 parent 4e6c022 commit 56cf00c
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .changeset/slow-paws-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'integration-tests': patch
'@aws-amplify/data-schema': patch
---

Fix DeepPickFromPath unable to flatten arrays in a join table
16 changes: 13 additions & 3 deletions packages/data-schema/src/runtime/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,26 @@ type DeepPickFromPath<
? Head extends keyof FlatModel
? Tail extends '*'
? {
[k in Head]: NonRelationshipFields<
UnwrapArray<FlatModel[Head]>
>;
[k in Head]: FlatModel[Head] extends Record<string, unknown>
? NonRelationshipFields<FlattenArrays<FlatModel[Head]>>
: NonRelationshipFields<UnwrapArray<FlatModel[Head]>>;
}
: { [k in Head]: DeepPickFromPath<FlatModel[Head], Tail> }
: never
: Path extends keyof FlatModel
? { [K in Path]: FlatModel[Path] }
: never;

/**
* This mapped type gets called by DeepPickFromPath<FlatModel, Path> when user uses selection
* set on a query to a many-to-many relationship join table. It flattens the Arrays of Models referenced in the join table.
*
* (e.g. { customer: { orders: Order[]} } => { customer: { orders: Order} })
*/
type FlattenArrays<T> = {
[K in keyof T]: UnwrapArray<T[K]>;
};

/**
* Generates custom selection set type with up to 6 levels of nested fields
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1107,4 +1107,159 @@ describe('Custom selection set edge cases', () => {
type _test = Expect<Equal<ActualType, ExpectedTodoType>>;
});
});

describe('optional array with many-to-many relationships', () => {
const schema = a
.schema({
AlphabetEnum: a.enum(['A', 'B', 'C', 'D']),
Customer: a.model({
name: a.string().required(),
verbiageReqReq: a.ref('Verbiage').required().array().required(),
verbiageOptReq: a.ref('Verbiage').array().required(),
verbiageReqOpt: a.ref('Verbiage').required().array(),
verbiageOptOpt: a.ref('Verbiage').array(),
orders: a.hasMany('CustomerPart', 'customerId'),
}),
Verbiage: a.customType({
name: a.ref('AlphabetEnum').required(),
paragraphs: a.string().required().array().required(),
}),
CustomerPart: a.model({
customer: a.belongsTo('Customer', 'customerId'),
customerId: a.string(),
experience: a.string(),
part: a.belongsTo('Part', 'partId'),
partId: a.string(),
}),
Part: a.model({
name: a.string().required(),
orders: a.hasMany('CustomerPart', 'partId'),
}),
})
.authorization((allow) => allow.guest());

type Schema = ClientSchema<typeof schema>;

async function getMockedClient(
operationName: string,
mockedResult: object,
) {
const { spy, generateClient } = mockedGenerateClient([
{
data: {
[operationName]: {
items: [mockedResult],
},
},
},
]);

const config = await buildAmplifyConfig(schema);
Amplify.configure(config);
const client = generateClient<Schema>();
return { client, spy };
}

describe('Defined many-to-many relations', () => {
const sampleCustomerPart = {
customer: [
{
name: 'some-customer',
verbiageReqReq: [
{
name: 'A',
paragraphs: ['some-paragraph'],
},
],
verbiageOptReq: [
{
name: 'A',
paragraphs: ['some-paragraph'],
},
],
verbiageReqOpt: [
{
name: 'A',
paragraphs: ['some-paragraph'],
},
],
verbiageOptOpt: [
{
name: 'A',
paragraphs: ['some-paragraph'],
},
],
},
],
customerId: 'some-customer-id',
part: [
{
name: 'some-part',
orders: [
{
customerId: 'some-customer-id',
partId: 'some-part-id',
},
],
},
],
partId: 'some-part-id',
};

async function mockedOperation() {
const { client, spy } = await getMockedClient(
'listCustomerPart',
sampleCustomerPart,
);

const { data } = await client.models.CustomerPart.list({
selectionSet: [
'customer.*',
'customer.verbiageReqReq.*',
'customer.verbiageOptReq.*',
'customer.verbiageReqOpt.*',
'customer.verbiageOptOpt.*',
],
});
return { data, spy };
}

type Verbiage = {
readonly name: 'A' | 'B' | 'C' | 'D';
readonly paragraphs: string[];
};

test('.required().array().required()', async () => {
const { data } = await mockedOperation();
const cus = data[0].customer.verbiageReqReq;
type ExpectedType = Verbiage[];
type ActualType = typeof cus;
type _test = Expect<Equal<ActualType, ExpectedType>>;
});

test('.array().required()', async () => {
const { data } = await mockedOperation();
const cus = data[0].customer.verbiageOptReq;
type ExpectedType = (Verbiage | null)[];
type ActualType = typeof cus;
type _test = Expect<Equal<ActualType, ExpectedType>>;
});

test('.required().array()', async () => {
const { data } = await mockedOperation();
const cus = data[0].customer.verbiageReqOpt;
type ExpectedType = Verbiage[] | null;
type ActualType = typeof cus;
type _test = Expect<Equal<ActualType, ExpectedType>>;
});

test('.array()', async () => {
const { data } = await mockedOperation();
const cus = data[0].customer.verbiageOptOpt;
type ExpectedType = (Verbiage | null)[] | null;
type ActualType = typeof cus;
type _test = Expect<Equal<ActualType, ExpectedType>>;
});
});
});
});

0 comments on commit 56cf00c

Please sign in to comment.