@decaf-ts/for-fabric 0.13.6 → 0.13.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/dist/for-fabric.cjs +1 -1
- package/dist/for-fabric.cjs.map +1 -1
- package/dist/for-fabric.js +1 -1
- package/dist/for-fabric.js.map +1 -1
- package/lib/cjs/client/indexes/generation.cjs +40 -1
- package/lib/cjs/client/indexes/generation.cjs.map +1 -1
- package/lib/cjs/contracts/ContractAdapter.cjs +173 -83
- package/lib/cjs/contracts/ContractAdapter.cjs.map +1 -1
- package/lib/cjs/contracts/FabricContractPaginator.cjs +1 -0
- package/lib/cjs/contracts/FabricContractPaginator.cjs.map +1 -1
- package/lib/cjs/contracts/FabricContractStatement.cjs +2 -5
- package/lib/cjs/contracts/FabricContractStatement.cjs.map +1 -1
- package/lib/cjs/version.cjs +3 -3
- package/lib/esm/client/indexes/generation.js +40 -1
- package/lib/esm/client/indexes/generation.js.map +1 -1
- package/lib/esm/contracts/ContractAdapter.js +173 -83
- package/lib/esm/contracts/ContractAdapter.js.map +1 -1
- package/lib/esm/contracts/FabricContractPaginator.js +1 -0
- package/lib/esm/contracts/FabricContractPaginator.js.map +1 -1
- package/lib/esm/contracts/FabricContractStatement.js +2 -5
- package/lib/esm/contracts/FabricContractStatement.js.map +1 -1
- package/lib/esm/version.js +3 -3
- package/lib/types/contracts/ContractAdapter.d.cts +4 -0
- package/lib/types/contracts/ContractAdapter.d.mts +4 -0
- package/lib/types/version.d.cts +3 -3
- package/lib/types/version.d.mts +3 -3
- package/package.json +1 -1
|
@@ -8,6 +8,45 @@ exports.writeIndexes = writeIndexes;
|
|
|
8
8
|
exports.writeDesignDocs = writeDesignDocs;
|
|
9
9
|
const for_couchdb_1 = require("@decaf-ts/for-couchdb");
|
|
10
10
|
const decorator_validation_1 = require("@decaf-ts/decorator-validation");
|
|
11
|
+
function withDefaultQueryPkTieBreaker(indexes, m) {
|
|
12
|
+
const pkField = decorator_validation_1.Model.pk(m);
|
|
13
|
+
return indexes.map((index) => {
|
|
14
|
+
const fields = index?.index?.fields;
|
|
15
|
+
if (!index?.name ||
|
|
16
|
+
typeof index.name !== "string" ||
|
|
17
|
+
!index.name.includes("defaultQuery") ||
|
|
18
|
+
!Array.isArray(fields) ||
|
|
19
|
+
fields.length === 0) {
|
|
20
|
+
return index;
|
|
21
|
+
}
|
|
22
|
+
const sortedFields = fields.filter((field) => typeof field === "object" && !Array.isArray(field));
|
|
23
|
+
if (sortedFields.length !== fields.length) {
|
|
24
|
+
return index;
|
|
25
|
+
}
|
|
26
|
+
const hasPk = fields.some((field) => {
|
|
27
|
+
if (!field || typeof field !== "object" || Array.isArray(field)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return Object.prototype.hasOwnProperty.call(field, pkField);
|
|
31
|
+
});
|
|
32
|
+
if (hasPk) {
|
|
33
|
+
return index;
|
|
34
|
+
}
|
|
35
|
+
const lastField = fields[fields.length - 1];
|
|
36
|
+
const lastDirection = String(Object.values(lastField || {})[0] || "asc").toLowerCase();
|
|
37
|
+
const direction = lastDirection === "desc" ? "desc" : "asc";
|
|
38
|
+
return Object.assign({}, index, {
|
|
39
|
+
index: Object.assign({}, index.index, {
|
|
40
|
+
fields: [
|
|
41
|
+
...fields,
|
|
42
|
+
{
|
|
43
|
+
[pkField]: direction,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
11
50
|
function ensureDirectoryExistence(filePath) {
|
|
12
51
|
const fs = require("fs");
|
|
13
52
|
const path = require("path");
|
|
@@ -19,7 +58,7 @@ function ensureDirectoryExistence(filePath) {
|
|
|
19
58
|
fs.mkdirSync(dirname);
|
|
20
59
|
}
|
|
21
60
|
function generateModelIndexes(m) {
|
|
22
|
-
return (0, for_couchdb_1.generateIndexes)([m]);
|
|
61
|
+
return withDefaultQueryPkTieBreaker((0, for_couchdb_1.generateIndexes)([m]), m);
|
|
23
62
|
}
|
|
24
63
|
function generateModelDesignDocs(m, accum) {
|
|
25
64
|
const views = (0, for_couchdb_1.generateViews)([m]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation.js","sourceRoot":"","sources":["generation.js"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"generation.js","sourceRoot":"","sources":["generation.js"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ exports.FabricContractAdapter = void 0;
|
|
|
4
4
|
exports.createdByOnFabricCreateUpdate = createdByOnFabricCreateUpdate;
|
|
5
5
|
const for_couchdb_1 = require("@decaf-ts/for-couchdb");
|
|
6
6
|
const decorator_validation_1 = require("@decaf-ts/decorator-validation");
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
7
8
|
const ContractContext_js_1 = require("./ContractContext.cjs");
|
|
8
9
|
const db_decorators_1 = require("@decaf-ts/db-decorators");
|
|
9
10
|
const fabric_contract_api_1 = require("fabric-contract-api");
|
|
@@ -93,6 +94,32 @@ async function createdByOnFabricCreateUpdate(context, data, key, model) {
|
|
|
93
94
|
*/
|
|
94
95
|
class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
95
96
|
static { this.PRIVATE_BOOKMARK_PREFIX = "__dcf_pvtbm__"; }
|
|
97
|
+
static stableStringify(value) {
|
|
98
|
+
if (value === null || typeof value !== "object") {
|
|
99
|
+
return JSON.stringify(value);
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
return `[${value
|
|
103
|
+
.map((v) => FabricContractAdapter.stableStringify(v))
|
|
104
|
+
.join(",")}]`;
|
|
105
|
+
}
|
|
106
|
+
const obj = value;
|
|
107
|
+
return `{${Object.keys(obj)
|
|
108
|
+
.sort()
|
|
109
|
+
.map((key) => `${JSON.stringify(key)}:${FabricContractAdapter.stableStringify(obj[key])}`)
|
|
110
|
+
.join(",")}}`;
|
|
111
|
+
}
|
|
112
|
+
static privateQueryHash(query) {
|
|
113
|
+
const relevant = {
|
|
114
|
+
selector: query.selector || {},
|
|
115
|
+
sort: query.sort || [],
|
|
116
|
+
fields: query.fields || undefined,
|
|
117
|
+
use_index: query.use_index || undefined,
|
|
118
|
+
};
|
|
119
|
+
return (0, crypto_1.createHash)("sha256")
|
|
120
|
+
.update(FabricContractAdapter.stableStringify(relevant))
|
|
121
|
+
.digest("base64url");
|
|
122
|
+
}
|
|
96
123
|
static parseSyntheticPrivateBookmark(bookmark) {
|
|
97
124
|
if (typeof bookmark !== "string")
|
|
98
125
|
return undefined;
|
|
@@ -105,14 +132,18 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
105
132
|
typeof parsed !== "object" ||
|
|
106
133
|
typeof parsed.sortField !== "string" ||
|
|
107
134
|
typeof parsed.direction !== "string" ||
|
|
108
|
-
typeof parsed.
|
|
135
|
+
typeof parsed.idField !== "string" ||
|
|
136
|
+
typeof parsed.lastId !== "string"
|
|
137
|
+
|| typeof parsed.queryHash !== "string") {
|
|
109
138
|
return undefined;
|
|
110
139
|
}
|
|
111
140
|
return {
|
|
112
141
|
sortField: parsed.sortField,
|
|
113
142
|
direction: parsed.direction === "desc" ? "desc" : "asc",
|
|
143
|
+
idField: parsed.idField,
|
|
114
144
|
lastValue: parsed.lastValue,
|
|
115
145
|
lastId: parsed.lastId,
|
|
146
|
+
queryHash: parsed.queryHash,
|
|
116
147
|
};
|
|
117
148
|
}
|
|
118
149
|
catch {
|
|
@@ -123,35 +154,87 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
123
154
|
return `${FabricContractAdapter.PRIVATE_BOOKMARK_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString("base64url")}`;
|
|
124
155
|
}
|
|
125
156
|
static buildPrivateCursorSelector(selector, sortField, direction, cursor) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
],
|
|
137
|
-
}
|
|
138
|
-
: {
|
|
139
|
-
$or: [
|
|
140
|
-
{ [sortField]: { $gt: cursor.lastValue } },
|
|
141
|
-
{
|
|
142
|
-
$and: [
|
|
143
|
-
{ [sortField]: { $eq: cursor.lastValue } },
|
|
144
|
-
{ id: { $gt: cursor.lastId } },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
],
|
|
157
|
+
const cmp = direction === "desc" ? "$lt" : "$gt";
|
|
158
|
+
if (cursor.idField === sortField) {
|
|
159
|
+
const continuation = {
|
|
160
|
+
[sortField]: {
|
|
161
|
+
[cmp]: cursor.lastValue,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
if (!Object.keys(selector || {}).length)
|
|
165
|
+
return continuation;
|
|
166
|
+
return {
|
|
167
|
+
$and: [selector, continuation],
|
|
148
168
|
};
|
|
169
|
+
}
|
|
170
|
+
const continuation = {
|
|
171
|
+
$or: [
|
|
172
|
+
{
|
|
173
|
+
[sortField]: {
|
|
174
|
+
[cmp]: cursor.lastValue,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
$and: [
|
|
179
|
+
{
|
|
180
|
+
[sortField]: {
|
|
181
|
+
$eq: cursor.lastValue,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
[cursor.idField]: {
|
|
186
|
+
[cmp]: cursor.lastId,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
149
193
|
if (!Object.keys(selector || {}).length)
|
|
150
194
|
return continuation;
|
|
151
195
|
return {
|
|
152
196
|
$and: [selector, continuation],
|
|
153
197
|
};
|
|
154
198
|
}
|
|
199
|
+
static normalizePrivateSort(query, idField = "id") {
|
|
200
|
+
if (!Array.isArray(query.sort) || query.sort.length === 0) {
|
|
201
|
+
throw new core_1.PagingError("Private collection pagination requires an explicit Mango sort");
|
|
202
|
+
}
|
|
203
|
+
const firstSort = Object.entries(query.sort[0] || {})[0];
|
|
204
|
+
if (!firstSort || typeof firstSort[0] !== "string") {
|
|
205
|
+
throw new core_1.PagingError("Private collection pagination requires a valid first sort field");
|
|
206
|
+
}
|
|
207
|
+
const sortField = firstSort[0];
|
|
208
|
+
const direction = String(firstSort[1] || "asc").toLowerCase();
|
|
209
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
210
|
+
throw new core_1.PagingError(`Unsupported private pagination sort direction: ${direction}`);
|
|
211
|
+
}
|
|
212
|
+
const hasIdTieBreaker = idField === sortField ||
|
|
213
|
+
query.sort.some((entry) => {
|
|
214
|
+
const [field, dir] = Object.entries(entry || {})[0] || [];
|
|
215
|
+
return (field === idField && String(dir || "").toLowerCase() === direction);
|
|
216
|
+
});
|
|
217
|
+
if (!hasIdTieBreaker && idField !== sortField) {
|
|
218
|
+
query.sort = [...query.sort, { [idField]: direction }];
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
sortField,
|
|
222
|
+
direction: direction,
|
|
223
|
+
idField,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
static parsePrivateResultValue(value) {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(value.toString("utf8"));
|
|
229
|
+
if (!parsed || typeof parsed !== "object") {
|
|
230
|
+
throw new Error("Private query result is not an object");
|
|
231
|
+
}
|
|
232
|
+
return parsed;
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
throw new db_decorators_1.SerializationError(`Failed to parse private query result while building bookmark: ${e}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
155
238
|
getClient() {
|
|
156
239
|
throw new core_1.UnsupportedError("Client is not supported in Fabric contracts");
|
|
157
240
|
}
|
|
@@ -545,30 +628,34 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
545
628
|
case "queryResultPaginated": {
|
|
546
629
|
const [stub, rawInput, limit, skip, bookmark, ...args] = argsList;
|
|
547
630
|
const { log } = thisArg["logCtx"](args, prop);
|
|
631
|
+
if (skip !== undefined && skip !== null && Number(skip) > 0) {
|
|
632
|
+
throw new core_1.PagingError("Private collection pagination does not support skip/offset pagination. Use the returned bookmark instead.");
|
|
633
|
+
}
|
|
548
634
|
const pageSize = Math.max(1, Number(limit) || 250);
|
|
549
635
|
const query = { ...rawInput };
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
636
|
+
const queryPkField = typeof query["__pkField"] === "string" &&
|
|
637
|
+
query["__pkField"].trim().length
|
|
638
|
+
? String(query["__pkField"])
|
|
639
|
+
: "id";
|
|
640
|
+
delete query["__pkField"];
|
|
641
|
+
const { sortField, direction: sortDirection, idField, } = FabricContractAdapter.normalizePrivateSort(query, queryPkField);
|
|
555
642
|
const syntheticCursor = FabricContractAdapter.parseSyntheticPrivateBookmark(bookmark);
|
|
556
|
-
|
|
643
|
+
if (bookmark !== undefined &&
|
|
557
644
|
bookmark !== null &&
|
|
558
645
|
bookmark !== "" &&
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// Synthetic mode advances using a cursor on the sorted field plus the document id.
|
|
564
|
-
query.limit = hasOpaqueBookmark ? pageSize : pageSize + 1;
|
|
646
|
+
!syntheticCursor) {
|
|
647
|
+
throw new core_1.PagingError("Private collection pagination only supports adapter-generated synthetic bookmarks");
|
|
648
|
+
}
|
|
649
|
+
log.debug(`Private paginated query input collection=${collection} limit=${limit} skip=${skip} bookmark=${bookmark} sortField=${sortField} direction=${sortDirection} synthetic=${Boolean(syntheticCursor)}`);
|
|
565
650
|
delete query.skip;
|
|
566
651
|
delete query.bookmark;
|
|
652
|
+
query.limit = pageSize + 1;
|
|
653
|
+
const queryHash = FabricContractAdapter.privateQueryHash(query);
|
|
567
654
|
if (syntheticCursor) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
query.
|
|
655
|
+
if (syntheticCursor.queryHash !== queryHash) {
|
|
656
|
+
throw new core_1.PagingError("Private collection bookmark does not match the current query");
|
|
657
|
+
}
|
|
658
|
+
query.selector = FabricContractAdapter.buildPrivateCursorSelector(query.selector || {}, syntheticCursor.sortField, syntheticCursor.direction, syntheticCursor);
|
|
572
659
|
}
|
|
573
660
|
log.debug(`Querying collection ${collection} for ${JSON.stringify(query)}`);
|
|
574
661
|
const response = await stub.getPrivateDataQueryResult(collection, JSON.stringify(query));
|
|
@@ -607,6 +694,28 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
607
694
|
await iterator.close();
|
|
608
695
|
}
|
|
609
696
|
log.debug(`Private paginated collection=${collection} produced ${paged.length} rows hasMore=${hasMore}`);
|
|
697
|
+
let nextBookmark = "";
|
|
698
|
+
if (hasMore && paged.length) {
|
|
699
|
+
const last = paged[paged.length - 1];
|
|
700
|
+
const lastDoc = FabricContractAdapter.parsePrivateResultValue(last.value);
|
|
701
|
+
if (!(sortField in lastDoc)) {
|
|
702
|
+
throw new core_1.PagingError(`Cannot build private pagination bookmark: sorted field "${sortField}" is missing from the last result`);
|
|
703
|
+
}
|
|
704
|
+
const lastIdValue = lastDoc[idField];
|
|
705
|
+
if (lastIdValue === undefined ||
|
|
706
|
+
lastIdValue === null) {
|
|
707
|
+
throw new core_1.PagingError(`Cannot build private pagination bookmark: id field "${idField}" is missing from the last result`);
|
|
708
|
+
}
|
|
709
|
+
nextBookmark =
|
|
710
|
+
FabricContractAdapter.buildSyntheticPrivateBookmark({
|
|
711
|
+
sortField,
|
|
712
|
+
direction: sortDirection,
|
|
713
|
+
idField,
|
|
714
|
+
lastValue: lastDoc[sortField],
|
|
715
|
+
lastId: String(lastIdValue),
|
|
716
|
+
queryHash,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
610
719
|
// Wrap the page in an async iterator for resultIterator()
|
|
611
720
|
let idx = 0;
|
|
612
721
|
const arrayIterator = {
|
|
@@ -622,32 +731,7 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
622
731
|
iterator: arrayIterator,
|
|
623
732
|
metadata: {
|
|
624
733
|
fetchedRecordsCount: paged.length,
|
|
625
|
-
bookmark:
|
|
626
|
-
responseMetadata.bookmark
|
|
627
|
-
? responseMetadata.bookmark
|
|
628
|
-
: hasMore && paged.length
|
|
629
|
-
? FabricContractAdapter.buildSyntheticPrivateBookmark({
|
|
630
|
-
sortField,
|
|
631
|
-
direction: sortDirection,
|
|
632
|
-
lastValue: (() => {
|
|
633
|
-
try {
|
|
634
|
-
return JSON.parse(paged[paged.length - 1].value.toString("utf8"))?.[sortField];
|
|
635
|
-
}
|
|
636
|
-
catch {
|
|
637
|
-
return undefined;
|
|
638
|
-
}
|
|
639
|
-
})(),
|
|
640
|
-
lastId: (() => {
|
|
641
|
-
try {
|
|
642
|
-
const lastDoc = JSON.parse(paged[paged.length - 1].value.toString("utf8"));
|
|
643
|
-
return String(lastDoc?.id || paged[paged.length - 1].key);
|
|
644
|
-
}
|
|
645
|
-
catch {
|
|
646
|
-
return String(paged[paged.length - 1].key);
|
|
647
|
-
}
|
|
648
|
-
})(),
|
|
649
|
-
})
|
|
650
|
-
: "",
|
|
734
|
+
bookmark: nextBookmark,
|
|
651
735
|
},
|
|
652
736
|
};
|
|
653
737
|
}
|
|
@@ -850,46 +934,52 @@ class FabricContractAdapter extends for_couchdb_1.CouchDBAdapter {
|
|
|
850
934
|
const { log, ctx, ctxArgs } = this.logCtx(args, this.raw);
|
|
851
935
|
const enableSegregates = !args.length || args[0] !== true;
|
|
852
936
|
const fullySegregated = enableSegregates && ctx.isFullySegregated;
|
|
853
|
-
const {
|
|
854
|
-
const
|
|
855
|
-
const
|
|
937
|
+
const originalInput = { ...rawInput };
|
|
938
|
+
const { skip, limit } = originalInput;
|
|
939
|
+
const bookmark = originalInput["bookmark"];
|
|
940
|
+
const pkField = typeof originalInput["__pkField"] === "string" &&
|
|
941
|
+
originalInput["__pkField"].trim().length
|
|
942
|
+
? String(originalInput["__pkField"])
|
|
943
|
+
: "id";
|
|
944
|
+
const hasSkip = skip !== undefined && skip !== null && Number(skip) > 0;
|
|
945
|
+
const hasBookmark = bookmark !== undefined && bookmark !== null && bookmark !== "";
|
|
946
|
+
const paginationActive = Boolean(limit || hasSkip || hasBookmark);
|
|
947
|
+
const shouldPaginate = Boolean(paginationActive && (enableSegregates || hasSkip || hasBookmark));
|
|
948
|
+
const baseInput = { ...originalInput };
|
|
949
|
+
delete baseInput["skip"];
|
|
950
|
+
delete baseInput["bookmark"];
|
|
951
|
+
delete baseInput["__pkField"];
|
|
856
952
|
let resp = { docs: [], bookmark: undefined };
|
|
857
|
-
log.debug(`raw query start fullySegregated=${fullySegregated} enableSegregates=${enableSegregates} paginationActive=${paginationActive} limit=${limit} skip=${skip} bookmark=${bookmark} query=${JSON.stringify(
|
|
953
|
+
log.debug(`raw query start fullySegregated=${fullySegregated} enableSegregates=${enableSegregates} paginationActive=${paginationActive} limit=${limit} skip=${skip} bookmark=${bookmark} pkField=${pkField} query=${JSON.stringify(originalInput)}`);
|
|
858
954
|
// Query public state only when the model is NOT fully segregated
|
|
859
955
|
if (!fullySegregated) {
|
|
860
956
|
let iterator;
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
delete
|
|
864
|
-
log.debug(`Retrieving public paginated iterator: limit: ${limit}/ skip: ${skip}`);
|
|
865
|
-
const response = (await this.queryResultPaginated(ctx.stub,
|
|
957
|
+
if (shouldPaginate) {
|
|
958
|
+
const paginatedInput = { ...baseInput };
|
|
959
|
+
delete paginatedInput["limit"];
|
|
960
|
+
log.debug(`Retrieving public paginated iterator: limit: ${limit}/ skip: ${skip} bookmark=${bookmark}`);
|
|
961
|
+
const response = (await this.queryResultPaginated(ctx.stub, paginatedInput, limit || 250, skip?.toString(), bookmark, ...[ctx]));
|
|
866
962
|
resp.bookmark = response.metadata.bookmark;
|
|
867
963
|
iterator = response.iterator;
|
|
868
964
|
log.debug(`Retrieved public paging iterator`);
|
|
869
|
-
log.debug(`public paginated response bookmark=${resp.bookmark} query=${JSON.stringify(
|
|
965
|
+
log.debug(`public paginated response bookmark=${resp.bookmark} query=${JSON.stringify(paginatedInput)}`);
|
|
870
966
|
}
|
|
871
967
|
else {
|
|
872
968
|
log.debug("Retrieving listing public iterator");
|
|
873
|
-
iterator = (await this.queryResult(ctx.stub,
|
|
969
|
+
iterator = (await this.queryResult(ctx.stub, { ...baseInput }, ctx));
|
|
874
970
|
}
|
|
875
971
|
log.debug(`Retrieved public listing iterator`);
|
|
876
972
|
resp.docs = (await this.resultIterator(log, iterator));
|
|
877
973
|
log.debug(`returning ${Array.isArray(resp.docs) ? resp.docs.length : 1} results`);
|
|
878
974
|
}
|
|
879
975
|
else {
|
|
880
|
-
// For fully segregated models, strip pagination fields from rawInput
|
|
881
|
-
// so the segregated query below can re-apply them cleanly
|
|
882
|
-
if (limit || skip) {
|
|
883
|
-
delete rawInput["limit"];
|
|
884
|
-
delete rawInput["skip"];
|
|
885
|
-
}
|
|
886
976
|
log.debug("Skipping public state query (fully segregated model)");
|
|
887
977
|
}
|
|
888
978
|
const collections = enableSegregates ? ctx.getReadCollections() : undefined;
|
|
889
979
|
log.debug(`read collections for raw query: ${JSON.stringify(collections || [])}`);
|
|
890
980
|
if (collections && collections.length) {
|
|
891
981
|
// Build a fresh input with limit/skip/bookmark restored
|
|
892
|
-
const segregatedInput = { ...
|
|
982
|
+
const segregatedInput = { ...baseInput };
|
|
893
983
|
if (limit)
|
|
894
984
|
segregatedInput.limit = limit;
|
|
895
985
|
if (skip)
|