@nkzw/fate 0.0.6 → 0.0.8
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 +111 -62
- package/lib/cli.mjs +68 -33
- package/lib/index.d.mts +6 -567
- package/lib/index.mjs +102 -29
- package/lib/server.d.mts +35 -29
- package/lib/server.mjs +19 -9
- package/lib/types-BPmcnouE.d.mts +594 -0
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -418,6 +418,10 @@ 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
|
+
}
|
|
421
425
|
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
422
426
|
const MutationKind = "__fate__mutation";
|
|
423
427
|
|
|
@@ -663,7 +667,7 @@ const emptySet$1 = /* @__PURE__ */ new Set();
|
|
|
663
667
|
*/
|
|
664
668
|
function wrapMutation(client, identifier) {
|
|
665
669
|
const config = client.getTypeConfig(identifier.entity);
|
|
666
|
-
return async ({ args, delete: deleteRecord, input, optimistic, view: view$1 }) => {
|
|
670
|
+
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view: view$1 }) => {
|
|
667
671
|
const id$1 = maybeGetId(config.getId, input);
|
|
668
672
|
const plan = view$1 ? getSelectionPlan(view$1, null) : void 0;
|
|
669
673
|
const viewSelection = plan?.paths;
|
|
@@ -678,7 +682,7 @@ function wrapMutation(client, identifier) {
|
|
|
678
682
|
const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
|
|
679
683
|
const selection = viewSelection || optimisticSelection ? new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
|
|
680
684
|
const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
|
|
681
|
-
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan);
|
|
685
|
+
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert);
|
|
682
686
|
if (deleteRecord) {
|
|
683
687
|
if (id$1 == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
|
|
684
688
|
client.deleteRecord(identifier.entity, id$1, snapshots, listSnapshots);
|
|
@@ -692,7 +696,7 @@ function wrapMutation(client, identifier) {
|
|
|
692
696
|
const select = collectImplicitSelectedPaths(result);
|
|
693
697
|
const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
|
|
694
698
|
const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
|
|
695
|
-
client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask);
|
|
699
|
+
client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
|
|
696
700
|
if (deleteRecord && id$1 != null) client.deleteRecord(identifier.entity, id$1);
|
|
697
701
|
const resultId = maybeGetId(config.getId, result);
|
|
698
702
|
if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
|
|
@@ -1001,16 +1005,19 @@ const getRequestCacheKey = (request) => {
|
|
|
1001
1005
|
for (const name of names) {
|
|
1002
1006
|
const item = request[name];
|
|
1003
1007
|
if (!item) continue;
|
|
1004
|
-
const viewSignature = getViewSignature(item.root);
|
|
1005
1008
|
if (isNodeItem(item)) {
|
|
1006
|
-
parts.push(`node:${name}:${item.type}:${
|
|
1009
|
+
parts.push(`node:${name}:${item.type}:${getViewSignature(item.view)}:${item.id}`);
|
|
1007
1010
|
continue;
|
|
1008
1011
|
}
|
|
1009
1012
|
if (isNodesItem(item)) {
|
|
1010
|
-
parts.push(`node:${name}:${item.type}:${
|
|
1013
|
+
parts.push(`node:${name}:${item.type}:${getViewSignature(item.view)}:${item.ids.map(serializeId).join(",")}`);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (isQueryItem(item)) {
|
|
1017
|
+
parts.push(`query:${name}:${item.type}:${getViewSignature(item.view)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1011
1018
|
continue;
|
|
1012
1019
|
}
|
|
1013
|
-
parts.push(`list:${name}:${item.type}:${
|
|
1020
|
+
parts.push(`list:${name}:${item.type}:${getViewSignature(item.list)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1014
1021
|
}
|
|
1015
1022
|
return parts.join("$");
|
|
1016
1023
|
};
|
|
@@ -1038,11 +1045,13 @@ const groupSelectionByPrefix = (select) => {
|
|
|
1038
1045
|
var FateClient = class {
|
|
1039
1046
|
constructor(options) {
|
|
1040
1047
|
this.parentLists = /* @__PURE__ */ new Map();
|
|
1048
|
+
this.rootLists = /* @__PURE__ */ new Map();
|
|
1041
1049
|
this.pending = /* @__PURE__ */ new Map();
|
|
1042
1050
|
this.optimisticMasks = /* @__PURE__ */ new Map();
|
|
1043
1051
|
this.optimisticByEntity = /* @__PURE__ */ new Map();
|
|
1044
1052
|
this.optimisticTokenCounter = 0;
|
|
1045
1053
|
this.requests = /* @__PURE__ */ new Map();
|
|
1054
|
+
this.rootRequests = /* @__PURE__ */ new Map();
|
|
1046
1055
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1047
1056
|
this.store = new Store();
|
|
1048
1057
|
this.viewDataCache = new ViewDataCache();
|
|
@@ -1092,6 +1101,30 @@ var FateClient = class {
|
|
|
1092
1101
|
}
|
|
1093
1102
|
}
|
|
1094
1103
|
}
|
|
1104
|
+
registerRootList(type, key) {
|
|
1105
|
+
let lists = this.rootLists.get(type);
|
|
1106
|
+
if (!lists) {
|
|
1107
|
+
lists = /* @__PURE__ */ new Set();
|
|
1108
|
+
this.rootLists.set(type, lists);
|
|
1109
|
+
}
|
|
1110
|
+
lists.add(key);
|
|
1111
|
+
}
|
|
1112
|
+
insertIntoRootLists(type, entityId, insert) {
|
|
1113
|
+
if (insert === "none") return;
|
|
1114
|
+
const listNames = this.rootLists.get(type);
|
|
1115
|
+
if (!listNames) return;
|
|
1116
|
+
for (const key of listNames) {
|
|
1117
|
+
const listState = this.store.getListState(key);
|
|
1118
|
+
if (!listState || listState.ids.includes(entityId)) continue;
|
|
1119
|
+
const ids = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
|
|
1120
|
+
const cursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : listState.cursors;
|
|
1121
|
+
this.store.setList(key, {
|
|
1122
|
+
cursors,
|
|
1123
|
+
ids,
|
|
1124
|
+
pagination: listState.pagination
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1095
1128
|
getTypeConfig(type) {
|
|
1096
1129
|
const config = this.types.get(type);
|
|
1097
1130
|
if (!config) throw new Error(`fate: Unknown entity type '${type}'.`);
|
|
@@ -1108,8 +1141,8 @@ var FateClient = class {
|
|
|
1108
1141
|
} : argsPayload ? { args: argsPayload } : input;
|
|
1109
1142
|
return await this.transport.mutate(key, requestInput, select);
|
|
1110
1143
|
}
|
|
1111
|
-
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask) {
|
|
1112
|
-
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask);
|
|
1144
|
+
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
1145
|
+
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert);
|
|
1113
1146
|
}
|
|
1114
1147
|
deleteRecord(type, id$1, snapshots, listSnapshots) {
|
|
1115
1148
|
const entityId = toEntityId(type, id$1);
|
|
@@ -1278,13 +1311,13 @@ var FateClient = class {
|
|
|
1278
1311
|
...connection.args,
|
|
1279
1312
|
...args
|
|
1280
1313
|
};
|
|
1281
|
-
const { argsPayload: argsPayload$1, plan: plan$1 } = this.
|
|
1314
|
+
const { argsPayload: argsPayload$1, plan: plan$1 } = this.resolveSelection(view$1, requestArgs$1);
|
|
1282
1315
|
const { items, pagination } = await this.transport.fetchList(connection.field, plan$1.paths, argsPayload$1);
|
|
1283
1316
|
if (!items) return this.store.getListState(connection.key);
|
|
1284
1317
|
const incomingIds$1 = [];
|
|
1285
1318
|
const incomingCursors$1 = [];
|
|
1286
1319
|
for (const entry of items) {
|
|
1287
|
-
const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1);
|
|
1320
|
+
const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1, null, null, "after");
|
|
1288
1321
|
incomingIds$1.push(id$1);
|
|
1289
1322
|
incomingCursors$1.push(entry.cursor);
|
|
1290
1323
|
}
|
|
@@ -1296,6 +1329,7 @@ var FateClient = class {
|
|
|
1296
1329
|
direction: isBackward ? "backward" : "forward",
|
|
1297
1330
|
hasCursorArg
|
|
1298
1331
|
});
|
|
1332
|
+
this.registerRootList(connection.type, connection.key);
|
|
1299
1333
|
this.store.setList(connection.key, nextListState$1);
|
|
1300
1334
|
return nextListState$1;
|
|
1301
1335
|
}
|
|
@@ -1305,7 +1339,7 @@ var FateClient = class {
|
|
|
1305
1339
|
...args
|
|
1306
1340
|
};
|
|
1307
1341
|
if (requestArgs.id === void 0 && owner.id) requestArgs.id = owner.id;
|
|
1308
|
-
const { argsPayload, plan } = this.
|
|
1342
|
+
const { argsPayload, plan } = this.resolveSelection(view$1, requestArgs);
|
|
1309
1343
|
const nodeSelection = plan.paths;
|
|
1310
1344
|
const scopedArgsPayload = argsPayload ? scopeArgsPayload(argsPayload, connection.field) : void 0;
|
|
1311
1345
|
const parentSelection = /* @__PURE__ */ new Set();
|
|
@@ -1396,7 +1430,7 @@ var FateClient = class {
|
|
|
1396
1430
|
for (const [name, item] of Object.entries(request)) {
|
|
1397
1431
|
const isNode = isNodeItem(item);
|
|
1398
1432
|
if (isNode || isNodesItem(item)) {
|
|
1399
|
-
const plan = getSelectionPlan(item.
|
|
1433
|
+
const plan = getSelectionPlan(item.view, null);
|
|
1400
1434
|
const fields = plan.paths;
|
|
1401
1435
|
const fieldsSignature = [...fields].slice().sort().join(",");
|
|
1402
1436
|
const argsSignature = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
|
|
@@ -1416,6 +1450,11 @@ var FateClient = class {
|
|
|
1416
1450
|
const missing = this.store.missingForSelection(entityId, fields);
|
|
1417
1451
|
if (fetchAll || missing.size > 0) group.ids.push(raw);
|
|
1418
1452
|
}
|
|
1453
|
+
} else if (isQueryItem(item)) {
|
|
1454
|
+
const { argsPayload, plan } = this.resolveSelection(item.view, item.args);
|
|
1455
|
+
const entityId = this.rootRequests.get(name);
|
|
1456
|
+
const missing = entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths;
|
|
1457
|
+
if (fetchAll || missing.size > 0) promises.push(this.fetchQueryAndNormalize(name, item, plan, argsPayload));
|
|
1419
1458
|
} else promises.push(this.fetchListAndNormalize(name, item));
|
|
1420
1459
|
}
|
|
1421
1460
|
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())]);
|
|
@@ -1424,13 +1463,19 @@ var FateClient = class {
|
|
|
1424
1463
|
for (const [name, item] of Object.entries(request)) {
|
|
1425
1464
|
const isNode = isNodeItem(item);
|
|
1426
1465
|
if (isNode || isNodesItem(item)) {
|
|
1427
|
-
const fields = getSelectionPlan(item.
|
|
1466
|
+
const fields = getSelectionPlan(item.view, null).paths;
|
|
1428
1467
|
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1429
1468
|
const entityId = toEntityId(item.type, raw);
|
|
1430
1469
|
if (this.store.missingForSelection(entityId, fields).size > 0) return false;
|
|
1431
1470
|
}
|
|
1432
1471
|
continue;
|
|
1433
1472
|
}
|
|
1473
|
+
if (isQueryItem(item)) {
|
|
1474
|
+
const { plan } = this.resolveSelection(item.view, item.args);
|
|
1475
|
+
const entityId = this.rootRequests.get(name);
|
|
1476
|
+
if ((entityId ? this.store.missingForSelection(entityId, plan.paths) : plan.paths).size > 0) return false;
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1434
1479
|
if (!this.store.getList(name)) return false;
|
|
1435
1480
|
}
|
|
1436
1481
|
return true;
|
|
@@ -1439,17 +1484,26 @@ var FateClient = class {
|
|
|
1439
1484
|
const result = {};
|
|
1440
1485
|
for (const [name, item] of Object.entries(request)) {
|
|
1441
1486
|
if (isNodeItem(item)) {
|
|
1442
|
-
result[name] = this.ref(item.type, item.id, item.
|
|
1487
|
+
result[name] = this.ref(item.type, item.id, item.view);
|
|
1443
1488
|
continue;
|
|
1444
1489
|
}
|
|
1445
1490
|
if (isNodesItem(item)) {
|
|
1446
|
-
result[name] = item.ids.map((id$1) => this.ref(item.type, id$1, item.
|
|
1491
|
+
result[name] = item.ids.map((id$1) => this.ref(item.type, id$1, item.view));
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (isQueryItem(item)) {
|
|
1495
|
+
const entityId = this.rootRequests.get(name);
|
|
1496
|
+
if (entityId) {
|
|
1497
|
+
const { id: id$1 } = parseEntityId(entityId);
|
|
1498
|
+
result[name] = createRef(item.type, id$1, item.view, { root: true });
|
|
1499
|
+
} else result[name] = null;
|
|
1447
1500
|
continue;
|
|
1448
1501
|
}
|
|
1449
1502
|
const listState = this.store.getListState(name);
|
|
1450
|
-
const
|
|
1503
|
+
const hasItems = item.list && typeof item.list === "object" && "items" in item.list;
|
|
1504
|
+
const nodeView = hasItems && item.list.items ? item.list.items.node ?? item.list : item.list;
|
|
1451
1505
|
const nodes = (listState?.ids ?? []).map((id$1) => this.rootListRef(id$1, nodeView));
|
|
1452
|
-
if (
|
|
1506
|
+
if (hasItems) {
|
|
1453
1507
|
const connection = {
|
|
1454
1508
|
items: nodes.map((node, index) => ({
|
|
1455
1509
|
cursor: listState?.cursors?.[index],
|
|
@@ -1457,7 +1511,7 @@ var FateClient = class {
|
|
|
1457
1511
|
})),
|
|
1458
1512
|
pagination: listState?.pagination
|
|
1459
1513
|
};
|
|
1460
|
-
const { argsPayload } = this.
|
|
1514
|
+
const { argsPayload } = this.resolveSelection(item.list, item.args);
|
|
1461
1515
|
const metadata = {
|
|
1462
1516
|
args: argsPayload,
|
|
1463
1517
|
field: name,
|
|
@@ -1485,9 +1539,16 @@ var FateClient = class {
|
|
|
1485
1539
|
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
1486
1540
|
for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
|
|
1487
1541
|
}
|
|
1542
|
+
async fetchQueryAndNormalize(name, item, plan, argsPayload) {
|
|
1543
|
+
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${name}'.`);
|
|
1544
|
+
const record = await this.transport.fetchQuery(name, plan.paths, argsPayload);
|
|
1545
|
+
if (!record || typeof record !== "object") return;
|
|
1546
|
+
const entityId = this.writeEntity(item.type, record, plan.paths, void 0, plan);
|
|
1547
|
+
this.rootRequests.set(name, entityId);
|
|
1548
|
+
}
|
|
1488
1549
|
async fetchListAndNormalize(name, item) {
|
|
1489
1550
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${name}'.`);
|
|
1490
|
-
const { argsPayload, plan } = this.
|
|
1551
|
+
const { argsPayload, plan } = this.resolveSelection(item.list, item.args);
|
|
1491
1552
|
const { items, pagination } = await this.transport.fetchList(name, plan.paths, argsPayload);
|
|
1492
1553
|
const ids = [];
|
|
1493
1554
|
const cursors = [];
|
|
@@ -1496,13 +1557,14 @@ var FateClient = class {
|
|
|
1496
1557
|
ids.push(id$1);
|
|
1497
1558
|
cursors.push(entry.cursor);
|
|
1498
1559
|
}
|
|
1560
|
+
this.registerRootList(item.type, name);
|
|
1499
1561
|
this.store.setList(name, {
|
|
1500
1562
|
cursors,
|
|
1501
1563
|
ids,
|
|
1502
1564
|
pagination
|
|
1503
1565
|
});
|
|
1504
1566
|
}
|
|
1505
|
-
|
|
1567
|
+
resolveSelection(view$1, args) {
|
|
1506
1568
|
const plan = getSelectionPlan(view$1, null);
|
|
1507
1569
|
const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
|
|
1508
1570
|
if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
|
|
@@ -1511,7 +1573,7 @@ var FateClient = class {
|
|
|
1511
1573
|
plan
|
|
1512
1574
|
};
|
|
1513
1575
|
}
|
|
1514
|
-
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask) {
|
|
1576
|
+
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
1515
1577
|
const config = this.types.get(type);
|
|
1516
1578
|
if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
|
|
1517
1579
|
const entityId = toEntityId(type, config.getId(record));
|
|
@@ -1614,10 +1676,12 @@ var FateClient = class {
|
|
|
1614
1676
|
if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
|
|
1615
1677
|
this.viewDataCache.invalidate(entityId);
|
|
1616
1678
|
this.store.merge(entityId, result, select);
|
|
1617
|
-
this.linkParentLists(type, entityId, result, snapshots);
|
|
1679
|
+
this.linkParentLists(type, entityId, result, snapshots, insert ?? "after");
|
|
1680
|
+
if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
|
|
1618
1681
|
return entityId;
|
|
1619
1682
|
}
|
|
1620
|
-
linkParentLists(type, entityId, record, snapshots) {
|
|
1683
|
+
linkParentLists(type, entityId, record, snapshots, insert) {
|
|
1684
|
+
if (insert === "none") return;
|
|
1621
1685
|
const parents = this.parentLists.get(type);
|
|
1622
1686
|
if (!parents) return;
|
|
1623
1687
|
for (const parent of parents) {
|
|
@@ -1631,11 +1695,11 @@ var FateClient = class {
|
|
|
1631
1695
|
if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
|
|
1632
1696
|
if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
|
|
1633
1697
|
this.viewDataCache.invalidate(parentId);
|
|
1634
|
-
const nextList = [...current, createNodeRef(entityId)];
|
|
1698
|
+
const nextList = insert === "before" ? [createNodeRef(entityId), ...current] : [...current, createNodeRef(entityId)];
|
|
1635
1699
|
const ids = nextList.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id$1) => id$1 != null);
|
|
1636
1700
|
const defaultListKey = getListKey(parentId, parent.field);
|
|
1637
1701
|
const defaultListState = this.store.getListState(defaultListKey);
|
|
1638
|
-
const nextDefaultCursors = defaultListState?.cursors
|
|
1702
|
+
const nextDefaultCursors = defaultListState?.cursors ? insert === "before" ? [void 0, ...defaultListState.cursors] : [...defaultListState.cursors, void 0] : defaultListState?.cursors;
|
|
1639
1703
|
this.store.setList(defaultListKey, {
|
|
1640
1704
|
cursors: nextDefaultCursors,
|
|
1641
1705
|
ids,
|
|
@@ -1644,8 +1708,8 @@ var FateClient = class {
|
|
|
1644
1708
|
const registeredLists = this.store.getListsForField(parentId, parent.field).filter(([key]) => key !== defaultListKey);
|
|
1645
1709
|
for (const [listKey, listState] of registeredLists) {
|
|
1646
1710
|
if (listState.ids.includes(entityId)) continue;
|
|
1647
|
-
const listIds = [...listState.ids, entityId];
|
|
1648
|
-
const listCursors = listState.cursors ? [...listState.cursors, void 0] : void 0;
|
|
1711
|
+
const listIds = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
|
|
1712
|
+
const listCursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : void 0;
|
|
1649
1713
|
this.store.setList(listKey, {
|
|
1650
1714
|
cursors: listCursors,
|
|
1651
1715
|
ids: listIds,
|
|
@@ -1806,7 +1870,7 @@ function createClient(options) {
|
|
|
1806
1870
|
* Builds a `Transport` backed by a tRPC client using the configured resolvers
|
|
1807
1871
|
* for by-id queries, lists, and mutations.
|
|
1808
1872
|
*/
|
|
1809
|
-
function createTRPCTransport({ byId, client, lists, mutations }) {
|
|
1873
|
+
function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
1810
1874
|
const transport = {
|
|
1811
1875
|
async fetchById(type, ids, select, args) {
|
|
1812
1876
|
const resolver = byId[type];
|
|
@@ -1825,6 +1889,15 @@ function createTRPCTransport({ byId, client, lists, mutations }) {
|
|
|
1825
1889
|
args,
|
|
1826
1890
|
select: [...select]
|
|
1827
1891
|
});
|
|
1892
|
+
},
|
|
1893
|
+
async fetchQuery(procedure, select, args) {
|
|
1894
|
+
if (!queries) throw new Error(`fate(trpc): No query resolvers configured; cannot call "${procedure}".`);
|
|
1895
|
+
const resolver = queries[procedure];
|
|
1896
|
+
if (!resolver) throw new Error(`fate(trpc): Missing query resolver for procedure "${procedure}"`);
|
|
1897
|
+
return resolver(client)({
|
|
1898
|
+
args,
|
|
1899
|
+
select: [...select]
|
|
1900
|
+
});
|
|
1828
1901
|
}
|
|
1829
1902
|
};
|
|
1830
1903
|
transport.mutate = async (procedure, input, select) => {
|
package/lib/server.d.mts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
|
+
import { t as AnyRecord } from "./types-BPmcnouE.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 };
|