@sanity-labs/client-fake-for-test 0.5.0 → 0.7.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 +15 -7
- package/dist/index.cjs +116 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -21
- package/dist/index.d.ts +135 -21
- package/dist/index.js +116 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,16 +59,15 @@ async function enforceWriteAccess(args) {
|
|
|
59
59
|
function defaultAclPath(projectId, dataset) {
|
|
60
60
|
return `/projects/${projectId}/datasets/${dataset}/acl`;
|
|
61
61
|
}
|
|
62
|
-
function makeRequestImpl(
|
|
63
|
-
const aclPath = accessControl.aclPath ?? defaultAclPath(projectId, dataset);
|
|
62
|
+
function makeRequestImpl(getAccess, projectId, dataset) {
|
|
64
63
|
return async (opts) => {
|
|
65
|
-
const path = opts.uri ?? opts.url;
|
|
64
|
+
const accessControl = getAccess(), aclPath = accessControl.aclPath ?? defaultAclPath(projectId, dataset), path = opts.uri ?? opts.url;
|
|
66
65
|
if (path === "/users/me")
|
|
67
66
|
return accessControl.currentUser;
|
|
68
67
|
if (path === aclPath)
|
|
69
68
|
return accessControl.grants;
|
|
70
69
|
throw fakeError(
|
|
71
|
-
`request(): no handler for path "${path}". With
|
|
70
|
+
`request(): no handler for path "${path}". With access control configured, the fake only serves "/users/me" and "${aclPath}". For any other endpoint, register a stub (client.stub("request", fn)) or intercept the HTTP call with a tool like MSW.`
|
|
72
71
|
);
|
|
73
72
|
};
|
|
74
73
|
}
|
|
@@ -137,7 +136,7 @@ function createPatch(documentId, executor) {
|
|
|
137
136
|
dec: (props) => (ops.push({ kind: "dec", props }), handle),
|
|
138
137
|
insert: (position, anchor, items) => (ops.push({ kind: "insert", position, anchor, items }), handle),
|
|
139
138
|
ifRevisionId: (rev) => (ifRev = rev, handle),
|
|
140
|
-
commit: () => executor.apply(documentId, ops, ifRev),
|
|
139
|
+
commit: (options) => executor.apply(documentId, ops, ifRev, options),
|
|
141
140
|
toJSON: () => ({
|
|
142
141
|
id: documentId,
|
|
143
142
|
ops: [...ops],
|
|
@@ -688,7 +687,7 @@ const ISSUES_URL = "https://github.com/sanity-labs/client-fake-for-test/issues",
|
|
|
688
687
|
"releases",
|
|
689
688
|
"createVersion",
|
|
690
689
|
"unpublishVersion",
|
|
691
|
-
"request (only with accessControl)",
|
|
690
|
+
"request (only with accessControl or a bound token)",
|
|
692
691
|
"config",
|
|
693
692
|
"withConfig"
|
|
694
693
|
], UNSUPPORTED_MEMBERS = {
|
|
@@ -737,8 +736,8 @@ const ISSUES_URL = "https://github.com/sanity-labs/client-fake-for-test/issues",
|
|
|
737
736
|
workaround: "Out of scope \u2014 seed documents directly or stub the call site."
|
|
738
737
|
},
|
|
739
738
|
request: {
|
|
740
|
-
what: "generic HTTP request hook \u2014 only wired up when
|
|
741
|
-
workaround: "Create the client with `accessControl` to enable request() (it serves /users/me + the ACL path)
|
|
739
|
+
what: "generic HTTP request hook \u2014 only wired up when the handle has an acting identity",
|
|
740
|
+
workaround: "Create the client with `accessControl`, or bind a registered token (`identities` + `token` / `withConfig({ token })`), to enable request() (it serves /users/me + the ACL path). Or register a stub."
|
|
742
741
|
},
|
|
743
742
|
dryRun: {
|
|
744
743
|
what: "not a method \u2014 did you mean the `{ dryRun }` mutation option?",
|
|
@@ -838,6 +837,25 @@ function wrapWithGuards(client, config) {
|
|
|
838
837
|
}
|
|
839
838
|
});
|
|
840
839
|
}
|
|
840
|
+
const OPTIONS_ARG_POSITION = {
|
|
841
|
+
fetch: 2,
|
|
842
|
+
getDocument: 1,
|
|
843
|
+
getDocuments: 1,
|
|
844
|
+
create: 1,
|
|
845
|
+
createOrReplace: 1,
|
|
846
|
+
createIfNotExists: 1,
|
|
847
|
+
delete: 1,
|
|
848
|
+
mutate: 1,
|
|
849
|
+
action: 1,
|
|
850
|
+
request: 0
|
|
851
|
+
};
|
|
852
|
+
function tagFromArgs(method, args) {
|
|
853
|
+
const position = OPTIONS_ARG_POSITION[method];
|
|
854
|
+
if (position === void 0) return;
|
|
855
|
+
const options = args[position];
|
|
856
|
+
if (!(typeof options != "object" || options === null || !("tag" in options)))
|
|
857
|
+
return options.tag;
|
|
858
|
+
}
|
|
841
859
|
const RECORDED_METHODS = /* @__PURE__ */ new Set([
|
|
842
860
|
"fetch",
|
|
843
861
|
"getDocument",
|
|
@@ -854,7 +872,21 @@ const RECORDED_METHODS = /* @__PURE__ */ new Set([
|
|
|
854
872
|
"createVersion",
|
|
855
873
|
"unpublishVersion",
|
|
856
874
|
"request"
|
|
857
|
-
]), DEFAULT_PROJECT_ID = "test", DEFAULT_DATASET = "test"
|
|
875
|
+
]), DEFAULT_PROJECT_ID = "test", DEFAULT_DATASET = "test";
|
|
876
|
+
function requireIdentity(backend, token, site) {
|
|
877
|
+
if (backend.identities.has(token)) return;
|
|
878
|
+
const known = [...backend.identities.keys()];
|
|
879
|
+
throw fakeError(
|
|
880
|
+
`${site}: unknown token "${token}" \u2014 no identity is registered for it. ` + (known.length > 0 ? `Registered tokens: ${known.map((t) => `"${t}"`).join(", ")}. ` : "No identities are registered. ") + "Register identities via createTestClient({ identities }) or client.addIdentity(token, identity)."
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
function rejectAmbiguousIdentity(site, config) {
|
|
884
|
+
if (config.token !== void 0 && config.accessControl !== void 0)
|
|
885
|
+
throw fakeError(
|
|
886
|
+
`${site}: \`token\` and \`accessControl\` are mutually exclusive \u2014 a handle acts either as a registered identity (token) or as an inline accessControl config, not both.`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
const registryKey = (projectId, dataset) => `${projectId}:${dataset}`, parseRegistryKey = (key) => {
|
|
858
890
|
const [projectId, dataset] = key.split(":");
|
|
859
891
|
return { projectId, dataset };
|
|
860
892
|
};
|
|
@@ -864,8 +896,11 @@ function createTestClient(options = {}) {
|
|
|
864
896
|
log: [],
|
|
865
897
|
stubs: /* @__PURE__ */ new Map(),
|
|
866
898
|
calls: [],
|
|
867
|
-
concurrentEdits: /* @__PURE__ */ new Map()
|
|
868
|
-
|
|
899
|
+
concurrentEdits: /* @__PURE__ */ new Map(),
|
|
900
|
+
identities: new Map(Object.entries(options.identities ?? {}))
|
|
901
|
+
}, registry = backend.registry;
|
|
902
|
+
rejectAmbiguousIdentity("createTestClient()", options), options.token !== void 0 && requireIdentity(backend, options.token, "createTestClient()");
|
|
903
|
+
const ensureStore = (projectId, dataset) => {
|
|
869
904
|
const key = registryKey(projectId, dataset);
|
|
870
905
|
let store = registry.get(key);
|
|
871
906
|
return store === void 0 && (store = createTestStore(), registry.set(key, store)), store;
|
|
@@ -881,7 +916,8 @@ function createTestClient(options = {}) {
|
|
|
881
916
|
defaultDataset,
|
|
882
917
|
options.accessControl,
|
|
883
918
|
options.perspective ?? "raw",
|
|
884
|
-
options.mutationGuard
|
|
919
|
+
options.mutationGuard,
|
|
920
|
+
options.token
|
|
885
921
|
);
|
|
886
922
|
}
|
|
887
923
|
function dedupeIds(results) {
|
|
@@ -934,13 +970,30 @@ function toTxnMutations(operations) {
|
|
|
934
970
|
}
|
|
935
971
|
];
|
|
936
972
|
}
|
|
937
|
-
function makeHandle(backend, projectId, dataset, accessControl, perspective, mutationGuard) {
|
|
938
|
-
const { registry } = backend,
|
|
973
|
+
function makeHandle(backend, projectId, dataset, accessControl, perspective, mutationGuard, token) {
|
|
974
|
+
const { registry } = backend, activeAccess = () => {
|
|
975
|
+
if (token === void 0) return accessControl;
|
|
976
|
+
const identity = backend.identities.get(token);
|
|
977
|
+
if (identity === void 0)
|
|
978
|
+
throw fakeError(`no identity is registered for token "${token}" (was it removed?).`);
|
|
979
|
+
return { currentUser: identity.user, grants: identity.grants };
|
|
980
|
+
}, requireActiveAccess = () => {
|
|
981
|
+
const access = activeAccess();
|
|
982
|
+
if (access === void 0)
|
|
983
|
+
throw fakeError("this handle has no acting identity (no token or accessControl bound).");
|
|
984
|
+
return access;
|
|
985
|
+
}, store = (() => {
|
|
939
986
|
const key = registryKey(projectId, dataset);
|
|
940
987
|
let s = registry.get(key);
|
|
941
988
|
return s === void 0 && (s = createTestStore(), registry.set(key, s)), s;
|
|
942
|
-
})(), concurrentEditKey = (id) => registryKey(projectId, dataset) + " " + id, record = (transactionId, mutations) => {
|
|
943
|
-
backend.log.push({
|
|
989
|
+
})(), concurrentEditKey = (id) => registryKey(projectId, dataset) + " " + id, record = (transactionId, mutations, tag) => {
|
|
990
|
+
backend.log.push({
|
|
991
|
+
transactionId,
|
|
992
|
+
projectId,
|
|
993
|
+
dataset,
|
|
994
|
+
mutations,
|
|
995
|
+
...tag !== void 0 ? { tag } : {}
|
|
996
|
+
});
|
|
944
997
|
}, writeResult = (documents, results, transactionId, opts) => formatMutationResult(
|
|
945
998
|
documents,
|
|
946
999
|
results,
|
|
@@ -982,20 +1035,24 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
982
1035
|
`ifRevisionId check failed: document "${id}" is at _rev="${existing._rev}" but the mutation required _rev="${ifRevisionId}". The document changed since you read it \u2014 re-read it and retry. (This mirrors Sanity's optimistic-locking 409.)`
|
|
983
1036
|
);
|
|
984
1037
|
}, gate = async (document, permission) => {
|
|
985
|
-
|
|
1038
|
+
const access = activeAccess();
|
|
1039
|
+
access !== void 0 && await enforceWriteAccess({
|
|
986
1040
|
document,
|
|
987
|
-
grants:
|
|
1041
|
+
grants: access.grants,
|
|
988
1042
|
permission,
|
|
989
|
-
userId:
|
|
1043
|
+
userId: access.currentUser.id
|
|
990
1044
|
});
|
|
991
1045
|
}, guardGate = async (action, before, after) => {
|
|
992
|
-
mutationGuard !== void 0 && await mutationGuard({ action, before, after, identity:
|
|
993
|
-
}, canRead = async (document) =>
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1046
|
+
mutationGuard !== void 0 && await mutationGuard({ action, before, after, identity: activeAccess()?.currentUser.id });
|
|
1047
|
+
}, canRead = async (document) => {
|
|
1048
|
+
const access = activeAccess();
|
|
1049
|
+
return access === void 0 ? !0 : canAccess({
|
|
1050
|
+
document,
|
|
1051
|
+
grants: access.grants,
|
|
1052
|
+
permission: "read",
|
|
1053
|
+
userId: access.currentUser.id
|
|
1054
|
+
});
|
|
1055
|
+
}, applyPatch = async (documentId, ops, ifRev) => {
|
|
999
1056
|
const existing = store.get(documentId);
|
|
1000
1057
|
if (existing === void 0)
|
|
1001
1058
|
throw fakeError(
|
|
@@ -1060,7 +1117,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1060
1117
|
doc === void 0 ? store.remove(id) : store.put(doc);
|
|
1061
1118
|
throw error;
|
|
1062
1119
|
}
|
|
1063
|
-
record(transactionId, mutations);
|
|
1120
|
+
record(transactionId, mutations, txnOptions?.tag);
|
|
1064
1121
|
const documents = touchedIds.map((id) => store.get(id)).filter((d) => d !== void 0);
|
|
1065
1122
|
return formatMutationResult(
|
|
1066
1123
|
documents,
|
|
@@ -1071,7 +1128,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1071
1128
|
);
|
|
1072
1129
|
}, accessVisibleDocs = async () => {
|
|
1073
1130
|
const all = store.all();
|
|
1074
|
-
if (
|
|
1131
|
+
if (activeAccess() === void 0) return all;
|
|
1075
1132
|
const out = [];
|
|
1076
1133
|
for (const doc of all)
|
|
1077
1134
|
await canRead(doc) && out.push(doc);
|
|
@@ -1121,7 +1178,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1121
1178
|
const promoted = promote2(doc);
|
|
1122
1179
|
await gate(promoted, "create"), await guardGate("create", null, promoted);
|
|
1123
1180
|
const transactionId = opts?.transactionId ?? cryptoRandomKey();
|
|
1124
|
-
return opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "create", doc }])), writeResult(
|
|
1181
|
+
return opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "create", doc }], opts?.tag)), writeResult(
|
|
1125
1182
|
[promoted],
|
|
1126
1183
|
[{ id: promoted._id, operation: "create" }],
|
|
1127
1184
|
transactionId,
|
|
@@ -1133,7 +1190,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1133
1190
|
const promoted = promote2(doc), existing = store.get(doc._id);
|
|
1134
1191
|
await gate(existing ?? promoted, existing !== void 0 ? "update" : "create"), await guardGate(existing !== void 0 ? "update" : "create", existing ?? null, promoted);
|
|
1135
1192
|
const transactionId = opts?.transactionId ?? cryptoRandomKey();
|
|
1136
|
-
opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "createOrReplace", doc }]));
|
|
1193
|
+
opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "createOrReplace", doc }], opts?.tag));
|
|
1137
1194
|
const operation = existing !== void 0 ? "update" : "create";
|
|
1138
1195
|
return writeResult(
|
|
1139
1196
|
[promoted],
|
|
@@ -1153,7 +1210,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1153
1210
|
opts
|
|
1154
1211
|
);
|
|
1155
1212
|
const promoted = promote2(doc);
|
|
1156
|
-
return await gate(promoted, "create"), await guardGate("create", null, promoted), opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "createIfNotExists", doc }])), writeResult(
|
|
1213
|
+
return await gate(promoted, "create"), await guardGate("create", null, promoted), opts?.dryRun !== !0 && (store.put(promoted), record(transactionId, [{ kind: "createIfNotExists", doc }], opts?.tag)), writeResult(
|
|
1157
1214
|
[promoted],
|
|
1158
1215
|
[{ id: promoted._id, operation: "create" }],
|
|
1159
1216
|
transactionId,
|
|
@@ -1166,7 +1223,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1166
1223
|
existing !== void 0 && await gate(existing, "update"), existing !== void 0 && await guardGate("delete", existing, null);
|
|
1167
1224
|
const transactionId = opts?.transactionId ?? cryptoRandomKey();
|
|
1168
1225
|
let removed = existing;
|
|
1169
|
-
return opts?.dryRun !== !0 && (removed = store.remove(id), removed !== void 0 && record(transactionId, [{ kind: "delete", id }])), opts?.returnDocuments === void 0 && opts?.returnFirst === void 0 ? removed !== void 0 ? { _id: id } : null : formatMutationResult(
|
|
1226
|
+
return opts?.dryRun !== !0 && (removed = store.remove(id), removed !== void 0 && record(transactionId, [{ kind: "delete", id }], opts?.tag)), opts?.returnDocuments === void 0 && opts?.returnFirst === void 0 ? removed !== void 0 ? { _id: id } : null : formatMutationResult(
|
|
1170
1227
|
removed !== void 0 ? [removed] : [],
|
|
1171
1228
|
removed !== void 0 ? [{ id, operation: "delete" }] : [],
|
|
1172
1229
|
transactionId,
|
|
@@ -1175,11 +1232,13 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1175
1232
|
);
|
|
1176
1233
|
},
|
|
1177
1234
|
patch: (documentId) => createPatch(documentId, {
|
|
1178
|
-
apply: async (docId, ops, ifRev) => {
|
|
1235
|
+
apply: async (docId, ops, ifRev, options) => {
|
|
1179
1236
|
const doc = await applyPatch(docId, ops, ifRev);
|
|
1180
|
-
return record(
|
|
1181
|
-
|
|
1182
|
-
|
|
1237
|
+
return record(
|
|
1238
|
+
cryptoRandomKey(),
|
|
1239
|
+
[{ kind: "patch", documentId: docId, ops, ...ifRev !== void 0 ? { ifRev } : {} }],
|
|
1240
|
+
options?.tag
|
|
1241
|
+
), doc;
|
|
1183
1242
|
}
|
|
1184
1243
|
}),
|
|
1185
1244
|
transaction: () => createTransaction({ apply: applyTransaction }),
|
|
@@ -1226,16 +1285,21 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1226
1285
|
versionId: `versions.${releaseId}.${publishedId}`,
|
|
1227
1286
|
publishedId
|
|
1228
1287
|
}),
|
|
1229
|
-
...accessControl !== void 0 ? { request: makeRequestImpl(
|
|
1230
|
-
config: () => ({ projectId, dataset, perspective }),
|
|
1231
|
-
withConfig: (config) =>
|
|
1232
|
-
backend,
|
|
1233
|
-
config.
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1288
|
+
...token !== void 0 || accessControl !== void 0 ? { request: makeRequestImpl(requireActiveAccess, projectId, dataset) } : {},
|
|
1289
|
+
config: () => ({ projectId, dataset, perspective, ...token !== void 0 ? { token } : {} }),
|
|
1290
|
+
withConfig: (config) => {
|
|
1291
|
+
rejectAmbiguousIdentity("withConfig()", config), config.token !== void 0 && requireIdentity(backend, config.token, "withConfig()");
|
|
1292
|
+
const rebinds = config.token !== void 0 || config.accessControl !== void 0;
|
|
1293
|
+
return makeHandle(
|
|
1294
|
+
backend,
|
|
1295
|
+
config.projectId ?? projectId,
|
|
1296
|
+
config.dataset ?? dataset,
|
|
1297
|
+
rebinds ? config.accessControl : accessControl,
|
|
1298
|
+
config.perspective ?? perspective,
|
|
1299
|
+
mutationGuard,
|
|
1300
|
+
rebinds ? config.token : token
|
|
1301
|
+
);
|
|
1302
|
+
},
|
|
1239
1303
|
seed: (documents) => store.seed(documents),
|
|
1240
1304
|
add: (documents) => store.add(documents),
|
|
1241
1305
|
reset: () => store.reset(),
|
|
@@ -1250,6 +1314,10 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1250
1314
|
backend.stubs.set(name, handler);
|
|
1251
1315
|
},
|
|
1252
1316
|
stubs: () => Object.fromEntries(backend.stubs),
|
|
1317
|
+
addIdentity: (identityToken, identity) => {
|
|
1318
|
+
backend.identities.set(identityToken, identity);
|
|
1319
|
+
},
|
|
1320
|
+
identities: () => Object.fromEntries(backend.identities),
|
|
1253
1321
|
mutations: () => backend.log.flatMap((entry) => entry.mutations),
|
|
1254
1322
|
transactions: () => [...backend.log],
|
|
1255
1323
|
clearLog: () => {
|
|
@@ -1264,8 +1332,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1264
1332
|
return wrapWithGuards(client, {
|
|
1265
1333
|
getStub: (name) => backend.stubs.get(name),
|
|
1266
1334
|
onCall: (method, args) => {
|
|
1267
|
-
const rec = { method, args, projectId, dataset };
|
|
1268
|
-
return backend.calls.push(rec), (outcome) => {
|
|
1335
|
+
const rec = { method, args, projectId, dataset }, tag = tagFromArgs(method, args);
|
|
1336
|
+
return tag !== void 0 && (rec.tag = tag), backend.calls.push(rec), (outcome) => {
|
|
1269
1337
|
"error" in outcome ? rec.error = outcome.error : rec.result = outcome.result;
|
|
1270
1338
|
};
|
|
1271
1339
|
},
|