Skip to content

Commit f467b70

Browse files
authored
Merge pull request #754 from gadget-inc/record-reload
Give records a reference to the model manager which loaded them
2 parents a443855 + 3700e0e commit f467b70

28 files changed

+307
-190
lines changed

packages/api-client-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/api-client-core",
3-
"version": "0.15.38",
3+
"version": "0.15.39",
44
"files": [
55
"Readme.md",
66
"dist/**/*"

packages/api-client-core/spec/GadgetRecord.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { AnyPublicModelManager } from "../src/AnyModelManager.js";
12
import { ChangeTracking, GadgetRecord } from "../src/GadgetRecord.js";
23
interface SampleBaseRecord {
34
id?: string;
@@ -38,6 +39,8 @@ const expectPersistedChanges = (record: GadgetRecord<SampleBaseRecord>, ...prope
3839
return _expectChanges(record, ChangeTracking.SinceLastPersisted, ...properties);
3940
};
4041

42+
const mockModelManager: AnyPublicModelManager = {} as any;
43+
4144
describe("GadgetRecord", () => {
4245
let productBaseRecord: SampleBaseRecord;
4346
beforeAll(() => {
@@ -48,6 +51,16 @@ describe("GadgetRecord", () => {
4851
};
4952
});
5053

54+
it("can be constructed with a base record and no model manager for backwards compatibility", () => {
55+
const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
56+
expect(product.id).toEqual("123");
57+
expect(product.name).toEqual("A cool product");
58+
});
59+
60+
it("can be constructed with a base record and a model manager", () => {
61+
new GadgetRecord<SampleBaseRecord>(productBaseRecord, mockModelManager);
62+
});
63+
5164
it("should respond toJSON, which returns the inner __gadget.fields properties", () => {
5265
const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
5366
expect(product.toJSON()).toEqual({

packages/api-client-core/spec/operationRunners.spec.ts

Lines changed: 55 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { diff } from "@n1ru4l/json-patch-plus";
44
import { CombinedError } from "@urql/core";
55
import nock from "nock";
66
import { BackgroundActionHandle } from "../src/BackgroundActionHandle.js";
7-
import type { AnyModelManager, GadgetErrorGroup, LimitToKnownKeys } from "../src/index.js";
7+
import type { AnyPublicModelManager, GadgetErrorGroup, LimitToKnownKeys } from "../src/index.js";
88
import {
99
GadgetConnection,
1010
actionRunner,
@@ -48,6 +48,7 @@ describe("type checks", () => {
4848
// eslint-disable-next-line jest/no-export
4949
describe("operationRunners", () => {
5050
let connection: GadgetConnection;
51+
let manager: AnyPublicModelManager;
5152
let query: string | undefined;
5253
let mockUrqlClient: MockUrqlClient;
5354

@@ -60,25 +61,26 @@ describe("operationRunners", () => {
6061
},
6162
});
6263
jest.spyOn(connection, "currentClient" as any, "get").mockReturnValue(mockUrqlClient as any);
64+
manager = { connection } as AnyPublicModelManager;
6365
});
6466

6567
describe("findOneRunner", () => {
6668
test("can execute a findOne operation against a model", async () => {
6769
const promise = findOneRunner({ connection }, "widget", "123", { id: true, name: true }, "widget");
6870

6971
expect(query).toMatchInlineSnapshot(`
70-
"query widget($id: GadgetID!) {
71-
widget(id: $id) {
72-
id
73-
name
74-
__typename
75-
}
76-
gadgetMeta {
77-
hydrations(modelName:
78-
"widget")
79-
}
80-
}"
81-
`);
72+
"query widget($id: GadgetID!) {
73+
widget(id: $id) {
74+
id
75+
name
76+
__typename
77+
}
78+
gadgetMeta {
79+
hydrations(modelName:
80+
"widget")
81+
}
82+
}"
83+
`);
8284

8385
mockUrqlClient.executeQuery.pushResponse("widget", {
8486
data: {
@@ -316,32 +318,32 @@ describe("operationRunners", () => {
316318

317319
describe("findManyRunner", () => {
318320
test("can execute a findMany operation against a model", async () => {
319-
const promise = findManyRunner({ connection } as AnyModelManager, "widgets", { id: true, name: true }, "widget");
321+
const promise = findManyRunner({ connection } as AnyPublicModelManager, "widgets", { id: true, name: true }, "widget");
320322

321323
expect(query).toMatchInlineSnapshot(`
322-
"query widgets($after: String, $first: Int, $before: String, $last: Int) {
323-
widgets(after: $after, first: $first, before: $before, last: $last) {
324-
pageInfo {
325-
hasNextPage
326-
hasPreviousPage
327-
startCursor
328-
endCursor
329-
}
330-
edges {
331-
cursor
332-
node {
333-
id
334-
name
335-
__typename
336-
}
337-
}
338-
}
339-
gadgetMeta {
340-
hydrations(modelName:
341-
"widget")
342-
}
343-
}"
344-
`);
324+
"query widgets($after: String, $first: Int, $before: String, $last: Int) {
325+
widgets(after: $after, first: $first, before: $before, last: $last) {
326+
pageInfo {
327+
hasNextPage
328+
hasPreviousPage
329+
startCursor
330+
endCursor
331+
}
332+
edges {
333+
cursor
334+
node {
335+
id
336+
name
337+
__typename
338+
}
339+
}
340+
}
341+
gadgetMeta {
342+
hydrations(modelName:
343+
"widget")
344+
}
345+
}"
346+
`);
345347

346348
mockUrqlClient.executeQuery.pushResponse("widgets", {
347349
data: {
@@ -369,7 +371,7 @@ describe("operationRunners", () => {
369371

370372
test("can execute a findMany operation against a namespaced model", async () => {
371373
const promise = findManyRunner(
372-
{ connection } as AnyModelManager,
374+
{ connection } as AnyPublicModelManager,
373375
"widgets",
374376
{ id: true, name: true },
375377
"widget",
@@ -436,7 +438,7 @@ describe("operationRunners", () => {
436438

437439
test("can execute a findMany operation against a namespaced model when the namespace is a string", async () => {
438440
const promise = findManyRunner(
439-
{ connection } as AnyModelManager,
441+
{ connection } as AnyPublicModelManager,
440442
"widgets",
441443
{ id: true, name: true },
442444
"widget",
@@ -502,9 +504,7 @@ describe("operationRunners", () => {
502504
describe("actionRunner", () => {
503505
test("can run a single create action", async () => {
504506
const promise = actionRunner<{ id: string; name: string }>(
505-
{
506-
connection,
507-
},
507+
manager,
508508
"createWidget",
509509
{ id: true, name: true },
510510
"widget",
@@ -544,9 +544,7 @@ describe("operationRunners", () => {
544544

545545
test("can run a single update action", async () => {
546546
const promise = actionRunner<{ id: string; name: string }>(
547-
{
548-
connection,
549-
},
547+
manager,
550548
"updateWidget",
551549
{ id: true, name: true },
552550
"widget",
@@ -591,9 +589,7 @@ describe("operationRunners", () => {
591589

592590
test("can run a single action with an object result type", async () => {
593591
const promise = actionRunner(
594-
{
595-
connection,
596-
},
592+
manager,
597593
"upsertWidget",
598594
{ id: true, name: true, eventAt: true },
599595
"widget",
@@ -646,9 +642,7 @@ describe("operationRunners", () => {
646642

647643
test("can run a single action with an object result type that has an inner return type", async () => {
648644
const promise = actionRunner(
649-
{
650-
connection,
651-
},
645+
manager,
652646
"upsertWidget",
653647
{ id: true, name: true, eventAt: true },
654648
"widget",
@@ -693,9 +687,7 @@ describe("operationRunners", () => {
693687

694688
test("can run an action with hasReturnType", async () => {
695689
const promise = actionRunner(
696-
{
697-
connection,
698-
},
690+
manager,
699691
"createWidget",
700692
{ id: true, name: true },
701693
"widget",
@@ -733,9 +725,7 @@ describe("operationRunners", () => {
733725

734726
test("can throw the error returned by the server for a single action", async () => {
735727
const promise = actionRunner<{ id: string; name: string }>(
736-
{
737-
connection,
738-
},
728+
manager,
739729
"updateWidget",
740730
{ id: true, name: true },
741731
"widget",
@@ -780,9 +770,7 @@ describe("operationRunners", () => {
780770

781771
test("can run a bulk action by ids", async () => {
782772
const promise = actionRunner<{ id: string; name: string }>(
783-
{
784-
connection,
785-
},
773+
manager,
786774
"bulkFlipWidgets",
787775
{ id: true, name: true },
788776
"widget",
@@ -830,9 +818,7 @@ describe("operationRunners", () => {
830818

831819
test("can run a bulk action with params", async () => {
832820
const promise = actionRunner<{ id: string; name: string }>(
833-
{
834-
connection,
835-
},
821+
manager,
836822
"bulkCreateWidgets",
837823
{ id: true, name: true },
838824
"widget",
@@ -880,9 +866,7 @@ describe("operationRunners", () => {
880866

881867
test("can run a bulk action with a returnType", async () => {
882868
const promise = actionRunner(
883-
{
884-
connection,
885-
},
869+
manager,
886870
"bulkCreateWidgets",
887871
{ id: true, name: true },
888872
"widget",
@@ -921,9 +905,7 @@ describe("operationRunners", () => {
921905

922906
test("can run a bulk action with an object returnType", async () => {
923907
const promise = actionRunner(
924-
{
925-
connection,
926-
},
908+
manager,
927909
"bulkUpsertWidgets",
928910
{ id: true, name: true },
929911
"widget",
@@ -970,9 +952,7 @@ describe("operationRunners", () => {
970952

971953
test("throws a nice error when a bulk action returns errors", async () => {
972954
const promise = actionRunner<{ id: string; name: string }>(
973-
{
974-
connection,
975-
},
955+
manager,
976956
"bulkCreateWidgets",
977957
{ id: true, name: true },
978958
"widget",
@@ -1010,9 +990,7 @@ describe("operationRunners", () => {
1010990

1011991
test("throws a nice error when a bulk action returns errors and data", async () => {
1012992
const promise = actionRunner<{ id: string; name: string }>(
1013-
{
1014-
connection,
1015-
},
993+
manager,
1016994
"bulkCreateWidgets",
1017995
{ id: true, name: true },
1018996
"widget",
@@ -1057,9 +1035,7 @@ describe("operationRunners", () => {
10571035

10581036
test("returns undefined when bulk action does not have a result", async () => {
10591037
const promise = actionRunner<{ id: string; name: string }>(
1060-
{
1061-
connection,
1062-
},
1038+
manager,
10631039
"bulkDeleteWidgets",
10641040
null,
10651041
"widget",
@@ -1958,7 +1934,7 @@ describe("operationRunners", () => {
19581934
test("can run a live findMany", async () => {
19591935
const iterator = asyncIterableToIterator(
19601936
findManyRunner<{ id: string; name: string }, { live: true }>(
1961-
{ connection } as AnyModelManager,
1937+
{ connection } as AnyPublicModelManager,
19621938
"widgets",
19631939
{ id: true, name: true },
19641940
"widget",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { GadgetConnection } from "./GadgetConnection.js";
2+
import type { FindFirstFunction, FindManyFunction, FindOneFunction, GetFunction } from "./GadgetFunctions.js";
3+
import type { GadgetRecord } from "./GadgetRecord.js";
4+
import type { InternalModelManager } from "./InternalModelManager.js";
5+
6+
export type AnyModelFinderMetadata = {
7+
/** The name of the GraphQL API field that should be called for this operation */
8+
operationName: string;
9+
/** The model's api identifier */
10+
modelApiIdentifier: string;
11+
/** What fields to select from the GraphQL API if no explicit selection is passed */
12+
defaultSelection: Record<string, any>;
13+
/** A namespace this operation is nested in. Absent for old clients or root-namespaced operations */
14+
namespace?: string | string[] | null;
15+
/** Type-time only type member used for strong typing of finders */
16+
selectionType: any;
17+
/** Type-time only type member used for strong typing of finders */
18+
optionsType: any;
19+
/** Type-time only type member used for strong typing of finders */
20+
schemaType: any | null;
21+
};
22+
23+
export type AnyFindOneFunc = FindOneFunction<any, any, any, any>;
24+
export type AnyFindManyFunc = FindManyFunction<any, any, any, any>;
25+
export type AnyFindFirstFunc = FindFirstFunction<any, any, any, any>;
26+
27+
/**
28+
* The manager class for a given model that uses the Public API, like `api.post` or `api.user`
29+
**/
30+
export interface AnyPublicModelManager<
31+
FindOneFunc extends AnyFindOneFunc = AnyFindOneFunc,
32+
FindManyFunc extends AnyFindManyFunc = AnyFindManyFunc,
33+
FindFirstFunc extends AnyFindFirstFunc = AnyFindFirstFunc
34+
> {
35+
connection: GadgetConnection;
36+
findOne: FindOneFunc;
37+
findMany: FindManyFunc;
38+
findFirst: FindFirstFunc;
39+
maybeFindFirst(options: any): Promise<GadgetRecord<any> | null>;
40+
maybeFindOne(id: string, options: any): Promise<GadgetRecord<any> | null>;
41+
}
42+
43+
/**
44+
* The manager class for a given single model that uses the Public API, like `api.session`
45+
**/
46+
export interface AnyPublicSingletonModelManager<GetFunc extends GetFunction<any, any, any, any> = GetFunction<any, any, any, any>> {
47+
connection: GadgetConnection;
48+
get: GetFunc;
49+
}
50+
51+
/**
52+
* Prior to 1.1 actions were defined to accept just a connection
53+
*/
54+
export interface AnyLegacyModelManager {
55+
connection: GadgetConnection;
56+
}
57+
58+
/**
59+
* Any model manager, either public or internal
60+
*/
61+
export type AnyModelManager = AnyPublicModelManager | AnyPublicSingletonModelManager | AnyLegacyModelManager | InternalModelManager;

0 commit comments

Comments
 (0)