@nkzw/fate 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -127
- package/lib/cli.mjs +82 -27
- package/lib/index.d.mts +8 -567
- package/lib/index.mjs +148 -41
- package/lib/server.d.mts +35 -29
- package/lib/server.mjs +19 -9
- package/lib/types-CNKGKRtz.d.mts +615 -0
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -418,6 +418,12 @@ function isNodeItem(item) {
|
|
|
418
418
|
function isNodesItem(item) {
|
|
419
419
|
return "ids" in item;
|
|
420
420
|
}
|
|
421
|
+
/** Indicates whether a request item represents a root query. */
|
|
422
|
+
function isQueryItem(item) {
|
|
423
|
+
return "view" in item && !("id" in item) && !("ids" in item);
|
|
424
|
+
}
|
|
425
|
+
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
426
|
+
const RootKind = "__fate__root";
|
|
421
427
|
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
422
428
|
const MutationKind = "__fate__mutation";
|
|
423
429
|
|
|
@@ -663,7 +669,7 @@ const emptySet$1 = /* @__PURE__ */ new Set();
|
|
|
663
669
|
*/
|
|
664
670
|
function wrapMutation(client, identifier) {
|
|
665
671
|
const config = client.getTypeConfig(identifier.entity);
|
|
666
|
-
return async ({ args, delete: deleteRecord, input, optimistic, view: view$1 }) => {
|
|
672
|
+
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view: view$1 }) => {
|
|
667
673
|
const id$1 = maybeGetId(config.getId, input);
|
|
668
674
|
const plan = view$1 ? getSelectionPlan(view$1, null) : void 0;
|
|
669
675
|
const viewSelection = plan?.paths;
|
|
@@ -678,7 +684,7 @@ function wrapMutation(client, identifier) {
|
|
|
678
684
|
const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
|
|
679
685
|
const selection = viewSelection || optimisticSelection ? new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
|
|
680
686
|
const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
|
|
681
|
-
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan);
|
|
687
|
+
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert);
|
|
682
688
|
if (deleteRecord) {
|
|
683
689
|
if (id$1 == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
|
|
684
690
|
client.deleteRecord(identifier.entity, id$1, snapshots, listSnapshots);
|
|
@@ -692,7 +698,7 @@ function wrapMutation(client, identifier) {
|
|
|
692
698
|
const select = collectImplicitSelectedPaths(result);
|
|
693
699
|
const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
|
|
694
700
|
const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
|
|
695
|
-
client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask);
|
|
701
|
+
client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
|
|
696
702
|
if (deleteRecord && id$1 != null) client.deleteRecord(identifier.entity, id$1);
|
|
697
703
|
const resultId = maybeGetId(config.getId, result);
|
|
698
704
|
if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
|
|
@@ -1001,16 +1007,19 @@ const getRequestCacheKey = (request) => {
|
|
|
1001
1007
|
for (const name of names) {
|
|
1002
1008
|
const item = request[name];
|
|
1003
1009
|
if (!item) continue;
|
|
1004
|
-
const viewSignature = getViewSignature(item.root);
|
|
1005
1010
|
if (isNodeItem(item)) {
|
|
1006
|
-
parts.push(`node:${name}:${item.
|
|
1011
|
+
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.id}`);
|
|
1007
1012
|
continue;
|
|
1008
1013
|
}
|
|
1009
1014
|
if (isNodesItem(item)) {
|
|
1010
|
-
parts.push(`node:${name}:${item.
|
|
1015
|
+
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.ids.map(serializeId).join(",")}`);
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (isQueryItem(item)) {
|
|
1019
|
+
parts.push(`query:${name}:${getViewSignature(item.view)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1011
1020
|
continue;
|
|
1012
1021
|
}
|
|
1013
|
-
parts.push(`list:${name}:${item.
|
|
1022
|
+
parts.push(`list:${name}:${getViewSignature(item.list)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1014
1023
|
}
|
|
1015
1024
|
return parts.join("$");
|
|
1016
1025
|
};
|
|
@@ -1038,22 +1047,25 @@ const groupSelectionByPrefix = (select) => {
|
|
|
1038
1047
|
var FateClient = class {
|
|
1039
1048
|
constructor(options) {
|
|
1040
1049
|
this.parentLists = /* @__PURE__ */ new Map();
|
|
1050
|
+
this.rootLists = /* @__PURE__ */ new Map();
|
|
1041
1051
|
this.pending = /* @__PURE__ */ new Map();
|
|
1042
1052
|
this.optimisticMasks = /* @__PURE__ */ new Map();
|
|
1043
1053
|
this.optimisticByEntity = /* @__PURE__ */ new Map();
|
|
1044
1054
|
this.optimisticTokenCounter = 0;
|
|
1045
1055
|
this.requests = /* @__PURE__ */ new Map();
|
|
1056
|
+
this.rootRequests = /* @__PURE__ */ new Map();
|
|
1046
1057
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1047
1058
|
this.store = new Store();
|
|
1048
1059
|
this.viewDataCache = new ViewDataCache();
|
|
1060
|
+
this.actions = Object.create(null);
|
|
1061
|
+
this.mutationMap = Object.create(null);
|
|
1062
|
+
this.mutations = Object.create(null);
|
|
1063
|
+
this.roots = options.roots;
|
|
1049
1064
|
this.transport = options.transport;
|
|
1050
1065
|
this.types = new Map(options.types.map((entity) => [entity.type, {
|
|
1051
1066
|
getId,
|
|
1052
1067
|
...entity
|
|
1053
1068
|
}]));
|
|
1054
|
-
this.mutationMap = Object.create(null);
|
|
1055
|
-
this.mutations = Object.create(null);
|
|
1056
|
-
this.actions = Object.create(null);
|
|
1057
1069
|
if (options.mutations) for (const [key, definition] of Object.entries(options.mutations)) {
|
|
1058
1070
|
const mutation$1 = wrapMutation(this, {
|
|
1059
1071
|
...definition,
|
|
@@ -1092,6 +1104,30 @@ var FateClient = class {
|
|
|
1092
1104
|
}
|
|
1093
1105
|
}
|
|
1094
1106
|
}
|
|
1107
|
+
registerRootList(type, key) {
|
|
1108
|
+
let lists = this.rootLists.get(type);
|
|
1109
|
+
if (!lists) {
|
|
1110
|
+
lists = /* @__PURE__ */ new Set();
|
|
1111
|
+
this.rootLists.set(type, lists);
|
|
1112
|
+
}
|
|
1113
|
+
lists.add(key);
|
|
1114
|
+
}
|
|
1115
|
+
insertIntoRootLists(type, entityId, insert) {
|
|
1116
|
+
if (insert === "none") return;
|
|
1117
|
+
const listNames = this.rootLists.get(type);
|
|
1118
|
+
if (!listNames) return;
|
|
1119
|
+
for (const key of listNames) {
|
|
1120
|
+
const listState = this.store.getListState(key);
|
|
1121
|
+
if (!listState || listState.ids.includes(entityId)) continue;
|
|
1122
|
+
const ids = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
|
|
1123
|
+
const cursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : listState.cursors;
|
|
1124
|
+
this.store.setList(key, {
|
|
1125
|
+
cursors,
|
|
1126
|
+
ids,
|
|
1127
|
+
pagination: listState.pagination
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1095
1131
|
getTypeConfig(type) {
|
|
1096
1132
|
const config = this.types.get(type);
|
|
1097
1133
|
if (!config) throw new Error(`fate: Unknown entity type '${type}'.`);
|
|
@@ -1108,8 +1144,8 @@ var FateClient = class {
|
|
|
1108
1144
|
} : argsPayload ? { args: argsPayload } : input;
|
|
1109
1145
|
return await this.transport.mutate(key, requestInput, select);
|
|
1110
1146
|
}
|
|
1111
|
-
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask) {
|
|
1112
|
-
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask);
|
|
1147
|
+
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
1148
|
+
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert);
|
|
1113
1149
|
}
|
|
1114
1150
|
deleteRecord(type, id$1, snapshots, listSnapshots) {
|
|
1115
1151
|
const entityId = toEntityId(type, id$1);
|
|
@@ -1278,13 +1314,13 @@ var FateClient = class {
|
|
|
1278
1314
|
...connection.args,
|
|
1279
1315
|
...args
|
|
1280
1316
|
};
|
|
1281
|
-
const { argsPayload: argsPayload$1, plan: plan$1 } = this.
|
|
1317
|
+
const { argsPayload: argsPayload$1, plan: plan$1 } = this.resolveSelection(view$1, requestArgs$1);
|
|
1282
1318
|
const { items, pagination } = await this.transport.fetchList(connection.field, plan$1.paths, argsPayload$1);
|
|
1283
1319
|
if (!items) return this.store.getListState(connection.key);
|
|
1284
1320
|
const incomingIds$1 = [];
|
|
1285
1321
|
const incomingCursors$1 = [];
|
|
1286
1322
|
for (const entry of items) {
|
|
1287
|
-
const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1);
|
|
1323
|
+
const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1, null, null, "after");
|
|
1288
1324
|
incomingIds$1.push(id$1);
|
|
1289
1325
|
incomingCursors$1.push(entry.cursor);
|
|
1290
1326
|
}
|
|
@@ -1296,6 +1332,7 @@ var FateClient = class {
|
|
|
1296
1332
|
direction: isBackward ? "backward" : "forward",
|
|
1297
1333
|
hasCursorArg
|
|
1298
1334
|
});
|
|
1335
|
+
this.registerRootList(connection.type, connection.key);
|
|
1299
1336
|
this.store.setList(connection.key, nextListState$1);
|
|
1300
1337
|
return nextListState$1;
|
|
1301
1338
|
}
|
|
@@ -1305,7 +1342,7 @@ var FateClient = class {
|
|
|
1305
1342
|
...args
|
|
1306
1343
|
};
|
|
1307
1344
|
if (requestArgs.id === void 0 && owner.id) requestArgs.id = owner.id;
|
|
1308
|
-
const { argsPayload, plan } = this.
|
|
1345
|
+
const { argsPayload, plan } = this.resolveSelection(view$1, requestArgs);
|
|
1309
1346
|
const nodeSelection = plan.paths;
|
|
1310
1347
|
const scopedArgsPayload = argsPayload ? scopeArgsPayload(argsPayload, connection.field) : void 0;
|
|
1311
1348
|
const parentSelection = /* @__PURE__ */ new Set();
|
|
@@ -1389,33 +1426,42 @@ var FateClient = class {
|
|
|
1389
1426
|
this.executeRequest(request, { fetchAll: true }).catch(() => {});
|
|
1390
1427
|
return result;
|
|
1391
1428
|
}
|
|
1429
|
+
getRootType(name) {
|
|
1430
|
+
const root = this.roots[name];
|
|
1431
|
+
if (!root) throw new Error(`fate: Unknown root request '${name}'.`);
|
|
1432
|
+
return root.type;
|
|
1433
|
+
}
|
|
1392
1434
|
async executeRequest(request, options = {}) {
|
|
1393
1435
|
const fetchAll = options.fetchAll ?? false;
|
|
1394
1436
|
const groups = /* @__PURE__ */ new Map();
|
|
1395
1437
|
const promises = [];
|
|
1396
1438
|
for (const [name, item] of Object.entries(request)) {
|
|
1439
|
+
const type = this.getRootType(name);
|
|
1397
1440
|
const isNode = isNodeItem(item);
|
|
1398
1441
|
if (isNode || isNodesItem(item)) {
|
|
1399
|
-
const plan = getSelectionPlan(item.
|
|
1442
|
+
const plan = getSelectionPlan(item.view, null);
|
|
1400
1443
|
const fields = plan.paths;
|
|
1401
|
-
const
|
|
1402
|
-
const argsSignature = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
|
|
1403
|
-
const groupKey = `${item.type}#${fieldsSignature}|${argsSignature}`;
|
|
1444
|
+
const groupKey = `${type}#${[...fields].slice().sort().join(",")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",")}`;
|
|
1404
1445
|
let group = groups.get(groupKey);
|
|
1405
1446
|
if (!group) {
|
|
1406
1447
|
group = {
|
|
1407
1448
|
fields,
|
|
1408
1449
|
ids: [],
|
|
1409
1450
|
plan,
|
|
1410
|
-
type
|
|
1451
|
+
type
|
|
1411
1452
|
};
|
|
1412
1453
|
groups.set(groupKey, group);
|
|
1413
1454
|
}
|
|
1414
1455
|
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1415
|
-
const entityId = toEntityId(
|
|
1456
|
+
const entityId = toEntityId(type, raw);
|
|
1416
1457
|
const missing = this.store.missingForSelection(entityId, fields);
|
|
1417
1458
|
if (fetchAll || missing.size > 0) group.ids.push(raw);
|
|
1418
1459
|
}
|
|
1460
|
+
} else if (isQueryItem(item)) {
|
|
1461
|
+
const { argsPayload, plan } = this.resolveSelection(item.view, item.args);
|
|
1462
|
+
const entityId = this.rootRequests.get(name);
|
|
1463
|
+
const missing = entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths;
|
|
1464
|
+
if (fetchAll || missing.size > 0) promises.push(this.fetchQueryAndNormalize(name, item, plan, argsPayload));
|
|
1419
1465
|
} else promises.push(this.fetchListAndNormalize(name, item));
|
|
1420
1466
|
}
|
|
1421
1467
|
await Promise.all([...promises, ...Array.from(groups.values()).map((group) => group.ids.length ? this.fetchByIdAndNormalize(group.type, group.ids, group.fields, group.plan) : Promise.resolve())]);
|
|
@@ -1423,14 +1469,21 @@ var FateClient = class {
|
|
|
1423
1469
|
hasRequestData(request) {
|
|
1424
1470
|
for (const [name, item] of Object.entries(request)) {
|
|
1425
1471
|
const isNode = isNodeItem(item);
|
|
1472
|
+
const type = this.getRootType(name);
|
|
1426
1473
|
if (isNode || isNodesItem(item)) {
|
|
1427
|
-
const fields = getSelectionPlan(item.
|
|
1474
|
+
const fields = getSelectionPlan(item.view, null).paths;
|
|
1428
1475
|
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1429
|
-
const entityId = toEntityId(
|
|
1476
|
+
const entityId = toEntityId(type, raw);
|
|
1430
1477
|
if (this.store.missingForSelection(entityId, fields).size > 0) return false;
|
|
1431
1478
|
}
|
|
1432
1479
|
continue;
|
|
1433
1480
|
}
|
|
1481
|
+
if (isQueryItem(item)) {
|
|
1482
|
+
const { plan } = this.resolveSelection(item.view, item.args);
|
|
1483
|
+
const entityId = this.rootRequests.get(name);
|
|
1484
|
+
if ((entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths).size > 0) return false;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1434
1487
|
if (!this.store.getList(name)) return false;
|
|
1435
1488
|
}
|
|
1436
1489
|
return true;
|
|
@@ -1438,18 +1491,28 @@ var FateClient = class {
|
|
|
1438
1491
|
getRequestResult(request) {
|
|
1439
1492
|
const result = {};
|
|
1440
1493
|
for (const [name, item] of Object.entries(request)) {
|
|
1494
|
+
const type = this.getRootType(name);
|
|
1441
1495
|
if (isNodeItem(item)) {
|
|
1442
|
-
result[name] = this.ref(
|
|
1496
|
+
result[name] = this.ref(type, item.id, item.view);
|
|
1443
1497
|
continue;
|
|
1444
1498
|
}
|
|
1445
1499
|
if (isNodesItem(item)) {
|
|
1446
|
-
result[name] = item.ids.map((id$1) => this.ref(
|
|
1500
|
+
result[name] = item.ids.map((id$1) => this.ref(type, id$1, item.view));
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (isQueryItem(item)) {
|
|
1504
|
+
const entityId = this.rootRequests.get(name);
|
|
1505
|
+
if (entityId) {
|
|
1506
|
+
const { id: id$1 } = parseEntityId(entityId);
|
|
1507
|
+
result[name] = createRef(type, id$1, item.view, { root: true });
|
|
1508
|
+
} else result[name] = null;
|
|
1447
1509
|
continue;
|
|
1448
1510
|
}
|
|
1449
1511
|
const listState = this.store.getListState(name);
|
|
1450
|
-
const
|
|
1512
|
+
const hasItems = item.list && typeof item.list === "object" && "items" in item.list;
|
|
1513
|
+
const nodeView = hasItems && item.list.items ? item.list.items.node ?? item.list : item.list;
|
|
1451
1514
|
const nodes = (listState?.ids ?? []).map((id$1) => this.rootListRef(id$1, nodeView));
|
|
1452
|
-
if (
|
|
1515
|
+
if (hasItems) {
|
|
1453
1516
|
const connection = {
|
|
1454
1517
|
items: nodes.map((node, index) => ({
|
|
1455
1518
|
cursor: listState?.cursors?.[index],
|
|
@@ -1457,7 +1520,7 @@ var FateClient = class {
|
|
|
1457
1520
|
})),
|
|
1458
1521
|
pagination: listState?.pagination
|
|
1459
1522
|
};
|
|
1460
|
-
const { argsPayload } = this.
|
|
1523
|
+
const { argsPayload } = this.resolveSelection(item.list, item.args);
|
|
1461
1524
|
const metadata = {
|
|
1462
1525
|
args: argsPayload,
|
|
1463
1526
|
field: name,
|
|
@@ -1465,7 +1528,7 @@ var FateClient = class {
|
|
|
1465
1528
|
owner: name,
|
|
1466
1529
|
procedure: `request.${name}`,
|
|
1467
1530
|
root: true,
|
|
1468
|
-
type
|
|
1531
|
+
type
|
|
1469
1532
|
};
|
|
1470
1533
|
Object.defineProperty(connection, ConnectionTag, {
|
|
1471
1534
|
configurable: false,
|
|
@@ -1485,24 +1548,33 @@ var FateClient = class {
|
|
|
1485
1548
|
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
1486
1549
|
for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
|
|
1487
1550
|
}
|
|
1551
|
+
async fetchQueryAndNormalize(name, item, plan, argsPayload) {
|
|
1552
|
+
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${name}'.`);
|
|
1553
|
+
const record = await this.transport.fetchQuery(name, plan.paths, argsPayload);
|
|
1554
|
+
if (!record || typeof record !== "object") return;
|
|
1555
|
+
const entityId = this.writeEntity(this.getRootType(name), record, plan.paths, void 0, plan);
|
|
1556
|
+
this.rootRequests.set(name, entityId);
|
|
1557
|
+
}
|
|
1488
1558
|
async fetchListAndNormalize(name, item) {
|
|
1489
1559
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${name}'.`);
|
|
1490
|
-
const
|
|
1560
|
+
const type = this.getRootType(name);
|
|
1561
|
+
const { argsPayload, plan } = this.resolveSelection(item.list, item.args);
|
|
1491
1562
|
const { items, pagination } = await this.transport.fetchList(name, plan.paths, argsPayload);
|
|
1492
1563
|
const ids = [];
|
|
1493
1564
|
const cursors = [];
|
|
1494
1565
|
for (const entry of items) {
|
|
1495
|
-
const id$1 = this.writeEntity(
|
|
1566
|
+
const id$1 = this.writeEntity(type, entry.node, plan.paths, void 0, plan);
|
|
1496
1567
|
ids.push(id$1);
|
|
1497
1568
|
cursors.push(entry.cursor);
|
|
1498
1569
|
}
|
|
1570
|
+
this.registerRootList(type, name);
|
|
1499
1571
|
this.store.setList(name, {
|
|
1500
1572
|
cursors,
|
|
1501
1573
|
ids,
|
|
1502
1574
|
pagination
|
|
1503
1575
|
});
|
|
1504
1576
|
}
|
|
1505
|
-
|
|
1577
|
+
resolveSelection(view$1, args) {
|
|
1506
1578
|
const plan = getSelectionPlan(view$1, null);
|
|
1507
1579
|
const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
|
|
1508
1580
|
if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
|
|
@@ -1511,7 +1583,7 @@ var FateClient = class {
|
|
|
1511
1583
|
plan
|
|
1512
1584
|
};
|
|
1513
1585
|
}
|
|
1514
|
-
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask) {
|
|
1586
|
+
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
1515
1587
|
const config = this.types.get(type);
|
|
1516
1588
|
if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
|
|
1517
1589
|
const entityId = toEntityId(type, config.getId(record));
|
|
@@ -1528,6 +1600,10 @@ var FateClient = class {
|
|
|
1528
1600
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
|
|
1529
1601
|
if (isFieldBlocked) continue;
|
|
1530
1602
|
const childPaths = selectionTree.get(key) ?? emptySet;
|
|
1603
|
+
if (value === null) {
|
|
1604
|
+
result[key] = null;
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1531
1607
|
if (value && typeof value === "object" && !isNodeRef(value)) {
|
|
1532
1608
|
const childType = relationDescriptor.type;
|
|
1533
1609
|
const childConfig = this.types.get(childType);
|
|
@@ -1538,6 +1614,10 @@ var FateClient = class {
|
|
|
1538
1614
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
|
|
1539
1615
|
if (isFieldBlocked) continue;
|
|
1540
1616
|
const childPaths = selectionTree.get(key) ?? emptySet;
|
|
1617
|
+
if (value === null) {
|
|
1618
|
+
result[key] = null;
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1541
1621
|
const childType = relationDescriptor.listOf;
|
|
1542
1622
|
const childConfig = this.types.get(childType);
|
|
1543
1623
|
if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
|
|
@@ -1614,10 +1694,12 @@ var FateClient = class {
|
|
|
1614
1694
|
if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
|
|
1615
1695
|
this.viewDataCache.invalidate(entityId);
|
|
1616
1696
|
this.store.merge(entityId, result, select);
|
|
1617
|
-
this.linkParentLists(type, entityId, result, snapshots);
|
|
1697
|
+
this.linkParentLists(type, entityId, result, snapshots, insert ?? "after");
|
|
1698
|
+
if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
|
|
1618
1699
|
return entityId;
|
|
1619
1700
|
}
|
|
1620
|
-
linkParentLists(type, entityId, record, snapshots) {
|
|
1701
|
+
linkParentLists(type, entityId, record, snapshots, insert) {
|
|
1702
|
+
if (insert === "none") return;
|
|
1621
1703
|
const parents = this.parentLists.get(type);
|
|
1622
1704
|
if (!parents) return;
|
|
1623
1705
|
for (const parent of parents) {
|
|
@@ -1631,11 +1713,11 @@ var FateClient = class {
|
|
|
1631
1713
|
if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
|
|
1632
1714
|
if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
|
|
1633
1715
|
this.viewDataCache.invalidate(parentId);
|
|
1634
|
-
const nextList = [...current, createNodeRef(entityId)];
|
|
1716
|
+
const nextList = insert === "before" ? [createNodeRef(entityId), ...current] : [...current, createNodeRef(entityId)];
|
|
1635
1717
|
const ids = nextList.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id$1) => id$1 != null);
|
|
1636
1718
|
const defaultListKey = getListKey(parentId, parent.field);
|
|
1637
1719
|
const defaultListState = this.store.getListState(defaultListKey);
|
|
1638
|
-
const nextDefaultCursors = defaultListState?.cursors
|
|
1720
|
+
const nextDefaultCursors = defaultListState?.cursors ? insert === "before" ? [void 0, ...defaultListState.cursors] : [...defaultListState.cursors, void 0] : defaultListState?.cursors;
|
|
1639
1721
|
this.store.setList(defaultListKey, {
|
|
1640
1722
|
cursors: nextDefaultCursors,
|
|
1641
1723
|
ids,
|
|
@@ -1644,8 +1726,8 @@ var FateClient = class {
|
|
|
1644
1726
|
const registeredLists = this.store.getListsForField(parentId, parent.field).filter(([key]) => key !== defaultListKey);
|
|
1645
1727
|
for (const [listKey, listState] of registeredLists) {
|
|
1646
1728
|
if (listState.ids.includes(entityId)) continue;
|
|
1647
|
-
const listIds = [...listState.ids, entityId];
|
|
1648
|
-
const listCursors = listState.cursors ? [...listState.cursors, void 0] : void 0;
|
|
1729
|
+
const listIds = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
|
|
1730
|
+
const listCursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : void 0;
|
|
1649
1731
|
this.store.setList(listKey, {
|
|
1650
1732
|
cursors: listCursors,
|
|
1651
1733
|
ids: listIds,
|
|
@@ -1681,6 +1763,10 @@ var FateClient = class {
|
|
|
1681
1763
|
if (!(key in target)) target[key] = {};
|
|
1682
1764
|
const nextSelection = Object.keys(selectionWithoutArgs).length ? selectionWithoutArgs : selectionValue;
|
|
1683
1765
|
const value = record$1[key];
|
|
1766
|
+
if (value == null) {
|
|
1767
|
+
target[key] = null;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1684
1770
|
if (Array.isArray(value)) if (nextSelection.items && typeof nextSelection.items === "object") {
|
|
1685
1771
|
const selection = nextSelection.items;
|
|
1686
1772
|
const fieldArgs = plan.args.get(fieldPath);
|
|
@@ -1806,7 +1892,7 @@ function createClient(options) {
|
|
|
1806
1892
|
* Builds a `Transport` backed by a tRPC client using the configured resolvers
|
|
1807
1893
|
* for by-id queries, lists, and mutations.
|
|
1808
1894
|
*/
|
|
1809
|
-
function createTRPCTransport({ byId, client, lists, mutations }) {
|
|
1895
|
+
function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
1810
1896
|
const transport = {
|
|
1811
1897
|
async fetchById(type, ids, select, args) {
|
|
1812
1898
|
const resolver = byId[type];
|
|
@@ -1825,6 +1911,15 @@ function createTRPCTransport({ byId, client, lists, mutations }) {
|
|
|
1825
1911
|
args,
|
|
1826
1912
|
select: [...select]
|
|
1827
1913
|
});
|
|
1914
|
+
},
|
|
1915
|
+
async fetchQuery(procedure, select, args) {
|
|
1916
|
+
if (!queries) throw new Error(`fate(trpc): No query resolvers configured; cannot call "${procedure}".`);
|
|
1917
|
+
const resolver = queries[procedure];
|
|
1918
|
+
if (!resolver) throw new Error(`fate(trpc): Missing query resolver for procedure "${procedure}"`);
|
|
1919
|
+
return resolver(client)({
|
|
1920
|
+
args,
|
|
1921
|
+
select: [...select]
|
|
1922
|
+
});
|
|
1828
1923
|
}
|
|
1829
1924
|
};
|
|
1830
1925
|
transport.mutate = async (procedure, input, select) => {
|
|
@@ -1839,4 +1934,16 @@ function createTRPCTransport({ byId, client, lists, mutations }) {
|
|
|
1839
1934
|
}
|
|
1840
1935
|
|
|
1841
1936
|
//#endregion
|
|
1842
|
-
|
|
1937
|
+
//#region src/root.ts
|
|
1938
|
+
/**
|
|
1939
|
+
* Defines a root query for an entity type, capturing the response shape.
|
|
1940
|
+
*/
|
|
1941
|
+
function clientRoot(type) {
|
|
1942
|
+
return Object.freeze({
|
|
1943
|
+
[RootKind]: true,
|
|
1944
|
+
type
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
//#endregion
|
|
1949
|
+
export { ConnectionTag, FateClient, clientRoot, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
|
package/lib/server.d.mts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
+
import { t as AnyRecord } from "./types-CNKGKRtz.mjs";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { TRPCProcedureBuilder } from "@trpc/server";
|
|
3
4
|
|
|
4
5
|
//#region src/server/dataView.d.ts
|
|
5
6
|
declare const dataViewFieldsKey: unique symbol;
|
|
6
|
-
type AnyRecord = Record<string, unknown>;
|
|
7
7
|
type ResolverSelect<Context> = AnyRecord | ((options: {
|
|
8
8
|
args?: AnyRecord;
|
|
9
9
|
context?: Context;
|
|
10
10
|
}) => AnyRecord | void);
|
|
11
11
|
type Bivariant<Fn extends (...args: Array<any>) => unknown> = {
|
|
12
|
-
|
|
13
|
-
}['
|
|
14
|
-
type ResolverResolve<Item$1 extends AnyRecord, Context> = Bivariant<(
|
|
15
|
-
|
|
16
|
-
context?: Context;
|
|
17
|
-
item: Item$1;
|
|
18
|
-
}) => Promise<unknown> | unknown>;
|
|
12
|
+
bivariance(...args: Parameters<Fn>): ReturnType<Fn>;
|
|
13
|
+
}['bivariance'];
|
|
14
|
+
type ResolverResolve<Item$1 extends AnyRecord, Result, Context> = Bivariant<(item: Item$1, context?: Context, args?: AnyRecord) => Promise<Result> | Result>;
|
|
15
|
+
type ResolverAuthorize<Item$1 extends AnyRecord, Context> = Bivariant<(item: Item$1, context?: Context, args?: AnyRecord) => Promise<boolean> | boolean>;
|
|
19
16
|
/**
|
|
20
17
|
* Field configuration for selecting and resolving a computed value on the backend.
|
|
21
18
|
*/
|
|
22
|
-
type ResolverField<Item$1 extends AnyRecord, Context> = {
|
|
19
|
+
type ResolverField<Item$1 extends AnyRecord, Result, Context> = {
|
|
20
|
+
authorize?: ResolverAuthorize<Item$1, Context>;
|
|
23
21
|
kind: 'resolver';
|
|
24
|
-
resolve: ResolverResolve<Item$1, Context>;
|
|
22
|
+
resolve: ResolverResolve<Item$1, Result, Context>;
|
|
25
23
|
select?: ResolverSelect<Context>;
|
|
26
24
|
};
|
|
27
|
-
type DataField<Item$1 extends AnyRecord
|
|
25
|
+
type DataField<Item$1 extends AnyRecord> = true | DataView<AnyRecord> | ResolverField<Item$1, any, any>;
|
|
28
26
|
/**
|
|
29
27
|
* Recursively serializes resolver results for transport across the network.
|
|
30
28
|
*/
|
|
@@ -33,15 +31,15 @@ type Serializable<T> = T extends Date ? string : T extends Array<infer U> ? Arra
|
|
|
33
31
|
* Server-side mirror of a view definition describing how to select and resolve
|
|
34
32
|
* fields when fulfilling a client request.
|
|
35
33
|
*/
|
|
36
|
-
type DataView<Item$1 extends AnyRecord
|
|
37
|
-
fields: Record<string, DataField<Item$1
|
|
34
|
+
type DataView<Item$1 extends AnyRecord> = {
|
|
35
|
+
fields: Record<string, DataField<Item$1>>;
|
|
38
36
|
kind?: 'resolver' | 'list';
|
|
39
37
|
typeName: string;
|
|
40
38
|
};
|
|
41
39
|
/**
|
|
42
40
|
* Convenience type for declaring the fields of a server data view.
|
|
43
41
|
*/
|
|
44
|
-
type DataViewConfig<Item$1 extends AnyRecord
|
|
42
|
+
type DataViewConfig<Item$1 extends AnyRecord> = Record<string, DataField<Item$1>>;
|
|
45
43
|
/**
|
|
46
44
|
* Declares a server data view that exposes an object's available fields to the client.
|
|
47
45
|
*
|
|
@@ -51,43 +49,44 @@ type DataViewConfig<Item$1 extends AnyRecord, Context> = Record<string, DataFiel
|
|
|
51
49
|
* title: true,
|
|
52
50
|
* });
|
|
53
51
|
*/
|
|
54
|
-
declare function dataView<Item$1 extends AnyRecord
|
|
52
|
+
declare function dataView<Item$1 extends AnyRecord>(typeName: string): <Fields extends DataViewConfig<Item$1>>(fields: Fields) => DataView<Item$1> & {
|
|
55
53
|
readonly [dataViewFieldsKey]: Fields;
|
|
56
54
|
};
|
|
57
55
|
/**
|
|
58
56
|
* Marks a data view as a list resolver so the server can respond with
|
|
59
57
|
* connection information.
|
|
60
58
|
*/
|
|
61
|
-
declare const list: <Item$1 extends AnyRecord
|
|
59
|
+
declare const list: <Item$1 extends AnyRecord>(view: DataView<Item$1>) => {
|
|
62
60
|
kind: "list";
|
|
63
|
-
fields: Record<string, DataField<Item$1
|
|
61
|
+
fields: Record<string, DataField<Item$1>>;
|
|
64
62
|
typeName: string;
|
|
65
63
|
};
|
|
66
64
|
/**
|
|
67
65
|
* Declares a resolver field inside a data view, optionally providing a
|
|
68
66
|
* selection for any data dependencies.
|
|
69
67
|
*/
|
|
70
|
-
declare function resolver<Item$1 extends AnyRecord, Context = unknown>(config: {
|
|
71
|
-
|
|
68
|
+
declare function resolver<Item$1 extends AnyRecord, Result = unknown, Context = unknown>(config: {
|
|
69
|
+
authorize?: ResolverAuthorize<Item$1, Context>;
|
|
70
|
+
resolve: ResolverResolve<Item$1, Result, Context>;
|
|
72
71
|
select?: ResolverSelect<Context>;
|
|
73
|
-
}): ResolverField<Item$1, Context>;
|
|
72
|
+
}): ResolverField<Item$1, Result, Context>;
|
|
74
73
|
type NonNullish<T> = Exclude<T, null | undefined>;
|
|
75
74
|
type WithNullish<Original, Value> = null extends Original ? undefined extends Original ? Value | null | undefined : Value | null : undefined extends Original ? Value | undefined : Value;
|
|
76
|
-
type ResolverResult<Field> = Field extends ResolverField<AnyRecord,
|
|
77
|
-
type RelationResult<ItemField, V extends DataView<AnyRecord
|
|
78
|
-
type ViewFieldConfig<V extends DataView<AnyRecord
|
|
75
|
+
type ResolverResult<Field> = Field extends ResolverField<AnyRecord, any, any> ? Field['authorize'] extends ResolverAuthorize<any, any> ? Awaited<ReturnType<Field['resolve']>> | null : Awaited<ReturnType<Field['resolve']>> : never;
|
|
76
|
+
type RelationResult<ItemField, V extends DataView<AnyRecord>> = NonNullish<ItemField> extends Array<unknown> ? WithNullish<ItemField, Array<RawDataViewResult<V>>> : WithNullish<ItemField, RawDataViewResult<V>>;
|
|
77
|
+
type ViewFieldConfig<V extends DataView<AnyRecord>> = V extends {
|
|
79
78
|
readonly [dataViewFieldsKey]: infer Fields;
|
|
80
79
|
} ? Fields : V['fields'];
|
|
81
|
-
type RawFieldResult<Item$1 extends AnyRecord, Key extends PropertyKey, Field extends DataField<Item$1
|
|
82
|
-
type RawDataViewResult<V extends DataView<AnyRecord
|
|
83
|
-
type DataViewResult<V extends DataView<AnyRecord
|
|
80
|
+
type RawFieldResult<Item$1 extends AnyRecord, Key extends PropertyKey, Field extends DataField<Item$1>> = Field extends true ? Key extends keyof Item$1 ? Item$1[Key] : never : Field extends DataView<infer ChildItem> ? Key extends keyof Item$1 ? RelationResult<Item$1[Key], DataView<ChildItem>> : never : Field extends ResolverField<Item$1, any, any> ? ResolverResult<Field> : never;
|
|
81
|
+
type RawDataViewResult<V extends DataView<AnyRecord>> = V extends DataView<infer Item> ? { [K in keyof ViewFieldConfig<V>]: RawFieldResult<Item, K, ViewFieldConfig<V>[K]> } : never;
|
|
82
|
+
type DataViewResult<V extends DataView<AnyRecord>> = Serializable<RawDataViewResult<V>>;
|
|
84
83
|
type WithTypename<T, Name extends string> = T & {
|
|
85
84
|
__typename: Name;
|
|
86
85
|
};
|
|
87
86
|
/**
|
|
88
87
|
* Resolved entity type from a data view for client use.
|
|
89
88
|
*/
|
|
90
|
-
type Entity<TView extends DataView<AnyRecord
|
|
89
|
+
type Entity<TView extends DataView<AnyRecord>, Name extends string, Replacements extends Record<string, unknown> = Record<never, never>> = WithTypename<Omit<DataViewResult<TView>, keyof Replacements> & Replacements, Name>;
|
|
91
90
|
/**
|
|
92
91
|
* Builds a resolver that applies a client's selection to a server data view,
|
|
93
92
|
* filtering fields, running nested resolvers, and shaping selects.
|
|
@@ -101,7 +100,7 @@ declare function createResolver<Item$1 extends AnyRecord, Context = unknown>({
|
|
|
101
100
|
args?: AnyRecord;
|
|
102
101
|
ctx?: Context;
|
|
103
102
|
select: Iterable<string>;
|
|
104
|
-
view: DataView<Item$1
|
|
103
|
+
view: DataView<Item$1>;
|
|
105
104
|
}): {
|
|
106
105
|
resolve: (item: Item$1) => Promise<AnyRecord>;
|
|
107
106
|
resolveMany: (items: Array<AnyRecord>) => Promise<Array<AnyRecord>>;
|
|
@@ -164,4 +163,11 @@ declare const withConnection: <TContext>(procedure: ProcedureLike<TContext>) =>
|
|
|
164
163
|
query: QueryFn<TContext, TItem, ConnectionInputWithAdditional<TAdditionalInput>>;
|
|
165
164
|
}) => ReturnType<ReturnType<ProcedureLike<TContext>["input"]>["query"]>;
|
|
166
165
|
//#endregion
|
|
167
|
-
|
|
166
|
+
//#region src/server/input.d.ts
|
|
167
|
+
declare const byIdInput: z.ZodObject<{
|
|
168
|
+
args: z.ZodOptional<z.ZodType<Record<string, unknown>, unknown, z.core.$ZodTypeInternals<Record<string, unknown>, unknown>>>;
|
|
169
|
+
ids: z.ZodArray<z.ZodString>;
|
|
170
|
+
select: z.ZodArray<z.ZodString>;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
//#endregion
|
|
173
|
+
export { type Entity, byIdInput, connectionArgs, createResolver, dataView, list, resolver, withConnection };
|
package/lib/server.mjs
CHANGED
|
@@ -374,24 +374,26 @@ const collectResolvers = (node, select, args$1, context) => {
|
|
|
374
374
|
}
|
|
375
375
|
for (const relation of node.relations.values()) collectResolvers(relation, select, args$1, context);
|
|
376
376
|
};
|
|
377
|
-
const resolveNode = async (options) => {
|
|
378
|
-
const { item, node, options: resolverOptions } = options;
|
|
377
|
+
const resolveNode = async ({ item, node, options: resolverOptions }) => {
|
|
379
378
|
if (!isRecord(item)) return item;
|
|
380
379
|
let result = null;
|
|
381
380
|
const assign = (key, value) => {
|
|
382
381
|
if (!result) result = { ...item };
|
|
383
382
|
result[key] = value;
|
|
384
383
|
};
|
|
385
|
-
const
|
|
384
|
+
const getItem = () => result ?? item;
|
|
386
385
|
for (const [field, resolver$1] of node.resolvers) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
386
|
+
if (resolver$1.authorize) {
|
|
387
|
+
if (!await resolver$1.authorize(getItem(), resolverOptions.context, resolverOptions.args)) {
|
|
388
|
+
assign(field, null);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const value = await resolver$1.resolve(getItem(), resolverOptions.context, resolverOptions.args);
|
|
391
393
|
if (value !== void 0) assign(field, value);
|
|
392
394
|
}
|
|
393
395
|
for (const [field, relationNode] of node.relations) {
|
|
394
|
-
const current =
|
|
396
|
+
const current = getItem()[field];
|
|
395
397
|
if (Array.isArray(current)) {
|
|
396
398
|
const resolved = await Promise.all(current.map((entry) => resolveNode({
|
|
397
399
|
item: entry,
|
|
@@ -447,4 +449,12 @@ function createResolver({ args: args$1, ctx, select: initialSelect, view }) {
|
|
|
447
449
|
}
|
|
448
450
|
|
|
449
451
|
//#endregion
|
|
450
|
-
|
|
452
|
+
//#region src/server/input.ts
|
|
453
|
+
const byIdInput = z.object({
|
|
454
|
+
args: connectionArgs,
|
|
455
|
+
ids: z.array(z.string().min(1)).nonempty(),
|
|
456
|
+
select: z.array(z.string())
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
//#endregion
|
|
460
|
+
export { byIdInput, connectionArgs, createResolver, dataView, list, resolver, withConnection };
|