@distrohelena/canton-typescript-sdk 0.1.41 → 0.1.42
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/cjs/query/canonical/query-normalizer.js +15 -1
- package/dist/cjs/query/grpc/grpc-package-relation-reader.js +9 -1
- package/dist/cjs/query/grpc/grpc-query-client.js +49 -6
- package/dist/cjs/query/grpc/grpc-relation-mapper.js +42 -0
- package/dist/query/canonical/query-normalizer.js +15 -1
- package/dist/query/grpc/grpc-package-relation-reader.d.ts +2 -0
- package/dist/query/grpc/grpc-package-relation-reader.js +9 -1
- package/dist/query/grpc/grpc-query-client.js +50 -7
- package/dist/query/grpc/grpc-relation-mapper.d.ts +9 -0
- package/dist/query/grpc/grpc-relation-mapper.js +41 -0
- package/package.json +1 -1
|
@@ -475,7 +475,7 @@ function provesActive(predicate) {
|
|
|
475
475
|
return false;
|
|
476
476
|
}
|
|
477
477
|
else if (predicate.kind === "scalar") {
|
|
478
|
-
return predicate
|
|
478
|
+
return provesActiveScalar(predicate);
|
|
479
479
|
}
|
|
480
480
|
else if (predicate.kind === "and") {
|
|
481
481
|
return predicate.children.some(provesActive);
|
|
@@ -485,6 +485,20 @@ function provesActive(predicate) {
|
|
|
485
485
|
}
|
|
486
486
|
return false;
|
|
487
487
|
}
|
|
488
|
+
/** A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the same fact as active: true. */
|
|
489
|
+
function provesActiveScalar(predicate) {
|
|
490
|
+
if (predicate.path.length !== 1) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
const [field] = predicate.path;
|
|
494
|
+
if (field === "active") {
|
|
495
|
+
return predicate.operator === "equals" && predicate.value === true;
|
|
496
|
+
}
|
|
497
|
+
else if (field === "archivedAt" || field === "archivedEventOffset") {
|
|
498
|
+
return predicate.operator === "is" && predicate.value === null;
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
488
502
|
function validateOperatorValue(operator, value, field, kind, nullable) {
|
|
489
503
|
if (operator === "is" || operator === "isNot") {
|
|
490
504
|
if (value !== null) {
|
|
@@ -23,6 +23,8 @@ exports.GrpcPackageRelationError = GrpcPackageRelationError;
|
|
|
23
23
|
class GrpcPackageRelationReader {
|
|
24
24
|
packageService;
|
|
25
25
|
packageLoader;
|
|
26
|
+
/** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
|
|
27
|
+
resolved = new Map();
|
|
26
28
|
constructor(packageService, packageLoader = new daml_lf_package_loader_js_1.DamlLfPackageLoader()) {
|
|
27
29
|
this.packageService = packageService;
|
|
28
30
|
this.packageLoader = packageLoader;
|
|
@@ -53,6 +55,10 @@ class GrpcPackageRelationReader {
|
|
|
53
55
|
return Object.freeze(packages.slice().sort((left, right) => left.id.localeCompare(right.id)));
|
|
54
56
|
}
|
|
55
57
|
async readPackageAsync(packageId) {
|
|
58
|
+
const cached = this.resolved.get(packageId);
|
|
59
|
+
if (cached !== undefined) {
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
56
62
|
let response;
|
|
57
63
|
try {
|
|
58
64
|
response = await this.packageService.getPackageAsync({ packageId });
|
|
@@ -88,12 +94,14 @@ class GrpcPackageRelationReader {
|
|
|
88
94
|
.map((template) => templateMetadata(packageId, packageName, module.name, template))),
|
|
89
95
|
...interfaceMetadata(packageId, packageName, result.rawPackage),
|
|
90
96
|
];
|
|
91
|
-
|
|
97
|
+
const metadata = Object.freeze({
|
|
92
98
|
id: packageId,
|
|
93
99
|
name: packageName,
|
|
94
100
|
version: requiredPackageText(pkg.packageVersion, "version"),
|
|
95
101
|
templates: Object.freeze(templates),
|
|
96
102
|
});
|
|
103
|
+
this.resolved.set(packageId, metadata);
|
|
104
|
+
return metadata;
|
|
97
105
|
}
|
|
98
106
|
catch (error) {
|
|
99
107
|
throw isGrpcPackageRelationError(error) ? error : new GrpcPackageRelationError(packageId, errorMessage(error));
|
|
@@ -88,14 +88,24 @@ class DefaultGrpcQueryDataProvider {
|
|
|
88
88
|
const history = await this.snapshots.readHistoryAsync(endInclusive);
|
|
89
89
|
const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
|
|
90
90
|
const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)(transactions);
|
|
91
|
+
const useCreationsOnlyMetadata = requiresPackageMetadata(closure)
|
|
92
|
+
&& query.relation !== "packages" && query.relation !== "contractTypes" && query.relation !== "exerciseTypes"
|
|
93
|
+
&& derivableFromCreationsOnly(closure);
|
|
91
94
|
const packageMetadata = !requiresPackageMetadata(closure)
|
|
92
95
|
? []
|
|
93
96
|
: query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes"
|
|
94
97
|
? await this.packages.readAllAsync()
|
|
95
|
-
:
|
|
98
|
+
: useCreationsOnlyMetadata
|
|
99
|
+
? (0, grpc_relation_mapper_js_1.contractTypeMetadataFromCreations)(fragment.creationIdentities)
|
|
100
|
+
: await this.packages.readPackagesAsync((0, grpc_relation_mapper_js_1.referencedGrpcPackageIds)(fragment));
|
|
101
|
+
// The closure guarantees this query never reads "exercises" here, but the replayed window can still
|
|
102
|
+
// contain unrelated exercises (createGrpcQueryDataset always resolves package metadata for every
|
|
103
|
+
// exercise in the fragment). contractTypeMetadataFromCreations only knows about templates, so drop
|
|
104
|
+
// the exercises this query doesn't need instead of paying for their canonical keys.
|
|
105
|
+
const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
|
|
96
106
|
return packageMetadata.length === 0
|
|
97
107
|
? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
|
|
98
|
-
: (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(
|
|
108
|
+
: (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
99
109
|
}
|
|
100
110
|
const endInclusive = cached?.activeAtOffset ?? (await this.options.stateService.getLedgerEndAsync({})).offset;
|
|
101
111
|
if (query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes") {
|
|
@@ -112,9 +122,16 @@ class DefaultGrpcQueryDataProvider {
|
|
|
112
122
|
}
|
|
113
123
|
const active = await this.snapshots.readActiveContractsAsync(endInclusive, partiesFor(query));
|
|
114
124
|
const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)([], active.activeContracts);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
if (!requiresPackageMetadata(closure)) {
|
|
126
|
+
return fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
|
|
127
|
+
}
|
|
128
|
+
// Reaching here guarantees query.relation === "contracts" with needsHistory === false, which (per
|
|
129
|
+
// predicateRequiresHistory/includesRequireHistory above) is only possible when the closure never
|
|
130
|
+
// touches "exercises"/"exerciseTypes"/"packages"/"transactions"/"events" — those unconditionally force
|
|
131
|
+
// history. So requiresPackageMetadata(closure) here can only be "contractTypes", and every contract in
|
|
132
|
+
// this ACS-only fragment already carries its own packageName — no Package Service call needed.
|
|
133
|
+
const packageMetadata = (0, grpc_relation_mapper_js_1.contractTypeMetadataFromCreations)(fragment.creationIdentities);
|
|
134
|
+
return (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
137
|
function requiresHistory(query) {
|
|
@@ -143,7 +160,15 @@ function predicateProvesActive(predicate) {
|
|
|
143
160
|
return false;
|
|
144
161
|
}
|
|
145
162
|
else if (predicate.kind === "scalar") {
|
|
146
|
-
|
|
163
|
+
if (predicate.path.length !== 1) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const [field] = predicate.path;
|
|
167
|
+
// A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the
|
|
168
|
+
// same fact as active: true (kept in sync with provesActive in canonical/query-normalizer.ts).
|
|
169
|
+
return field === "active"
|
|
170
|
+
? predicate.operator === "equals" && predicate.value === true
|
|
171
|
+
: (field === "archivedAt" || field === "archivedEventOffset") && predicate.operator === "is" && predicate.value === null;
|
|
147
172
|
}
|
|
148
173
|
else if (predicate.kind === "and") {
|
|
149
174
|
return predicate.children.some(predicateProvesActive);
|
|
@@ -156,6 +181,24 @@ function predicateProvesActive(predicate) {
|
|
|
156
181
|
function requiresPackageMetadata(closure) {
|
|
157
182
|
return closure.has("packages") || closure.has("contractTypes") || closure.has("exercises") || closure.has("exerciseTypes");
|
|
158
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* True when contractType metadata can be derived from the fetched creation events alone (see
|
|
186
|
+
* contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
|
|
187
|
+
* own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
|
|
188
|
+
* "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
|
|
189
|
+
* "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
|
|
190
|
+
* the template side — so it is not a reliable source for the choice owner's package name when a choice was
|
|
191
|
+
* exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
|
|
192
|
+
* equivalent at all.
|
|
193
|
+
*
|
|
194
|
+
* The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
|
|
195
|
+
* it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
|
|
196
|
+
* this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
|
|
197
|
+
* every exercise present — this function only knows about templates, not choices.
|
|
198
|
+
*/
|
|
199
|
+
function derivableFromCreationsOnly(closure) {
|
|
200
|
+
return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
|
|
201
|
+
}
|
|
159
202
|
function predicateRequiresHistory(relation, predicate) {
|
|
160
203
|
if (predicate === undefined || predicate.kind === "scalar") {
|
|
161
204
|
return false;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.mapGrpcQueryRelationFragment = mapGrpcQueryRelationFragment;
|
|
4
4
|
exports.createGrpcQueryDataset = createGrpcQueryDataset;
|
|
5
5
|
exports.referencedGrpcPackageIds = referencedGrpcPackageIds;
|
|
6
|
+
exports.contractTypeMetadataFromCreations = contractTypeMetadataFromCreations;
|
|
6
7
|
const validation_error_js_1 = require("../../core/errors/validation-error.js");
|
|
7
8
|
const query_dataset_js_1 = require("../canonical/query-dataset.js");
|
|
8
9
|
const public_identity_js_1 = require("../canonical/public-identity.js");
|
|
@@ -243,6 +244,46 @@ function referencedGrpcPackageIds(fragment) {
|
|
|
243
244
|
...fragment.typeIdentities.filter((identity) => identity.choice !== undefined).map((identity) => identity.packageId),
|
|
244
245
|
])].sort());
|
|
245
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
249
|
+
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
250
|
+
* {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
|
|
251
|
+
* so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
|
|
252
|
+
* can see the "packages" relation, so it is never observed.
|
|
253
|
+
*/
|
|
254
|
+
function contractTypeMetadataFromCreations(creationIdentities) {
|
|
255
|
+
const packages = new Map();
|
|
256
|
+
for (const creation of creationIdentities) {
|
|
257
|
+
const packageId = creation.representativePackageId ?? creation.creationPackageId;
|
|
258
|
+
const { moduleName, entityName } = creation.templateId;
|
|
259
|
+
let pkg = packages.get(packageId);
|
|
260
|
+
if (pkg === undefined) {
|
|
261
|
+
pkg = { name: creation.packageName, templates: new Map() };
|
|
262
|
+
packages.set(packageId, pkg);
|
|
263
|
+
}
|
|
264
|
+
else if (pkg.name !== creation.packageName) {
|
|
265
|
+
throw new validation_error_js_1.ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
|
|
266
|
+
}
|
|
267
|
+
const identity = `${moduleName}${entityName}`;
|
|
268
|
+
if (!pkg.templates.has(identity)) {
|
|
269
|
+
const templateFqn = `${pkg.name}:${moduleName}:${entityName}`;
|
|
270
|
+
pkg.templates.set(identity, Object.freeze({
|
|
271
|
+
moduleName,
|
|
272
|
+
entityName,
|
|
273
|
+
payloadType: "template",
|
|
274
|
+
aliases: Object.freeze([templateFqn, `${moduleName}:${entityName}`, entityName]),
|
|
275
|
+
templateFqn,
|
|
276
|
+
choices: Object.freeze([]),
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
|
|
281
|
+
id,
|
|
282
|
+
name: pkg.name,
|
|
283
|
+
version: "unresolved",
|
|
284
|
+
templates: Object.freeze([...pkg.templates.values()]),
|
|
285
|
+
})));
|
|
286
|
+
}
|
|
246
287
|
function compareTemplateMetadata(left, right) {
|
|
247
288
|
return left.package.id.localeCompare(right.package.id) || left.template.moduleName.localeCompare(right.template.moduleName) || left.template.entityName.localeCompare(right.template.entityName);
|
|
248
289
|
}
|
|
@@ -622,6 +663,7 @@ function creationDescriptor(event, offset) {
|
|
|
622
663
|
templateId: copyTemplate(template),
|
|
623
664
|
creationPackageId: template.packageId,
|
|
624
665
|
representativePackageId: nullableString(event.representativePackageId),
|
|
666
|
+
packageName: event.packageName,
|
|
625
667
|
payload: (0, grpc_query_value_mapper_js_1.mapGrpcQueryValue)({ sum: { oneofKind: "record", record: event.createArguments } }),
|
|
626
668
|
witnesses: immutableStrings(event.witnessParties, "created event witnesses", true),
|
|
627
669
|
createdAt,
|
|
@@ -466,7 +466,7 @@ function provesActive(predicate) {
|
|
|
466
466
|
return false;
|
|
467
467
|
}
|
|
468
468
|
else if (predicate.kind === "scalar") {
|
|
469
|
-
return predicate
|
|
469
|
+
return provesActiveScalar(predicate);
|
|
470
470
|
}
|
|
471
471
|
else if (predicate.kind === "and") {
|
|
472
472
|
return predicate.children.some(provesActive);
|
|
@@ -476,6 +476,20 @@ function provesActive(predicate) {
|
|
|
476
476
|
}
|
|
477
477
|
return false;
|
|
478
478
|
}
|
|
479
|
+
/** A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the same fact as active: true. */
|
|
480
|
+
function provesActiveScalar(predicate) {
|
|
481
|
+
if (predicate.path.length !== 1) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
const [field] = predicate.path;
|
|
485
|
+
if (field === "active") {
|
|
486
|
+
return predicate.operator === "equals" && predicate.value === true;
|
|
487
|
+
}
|
|
488
|
+
else if (field === "archivedAt" || field === "archivedEventOffset") {
|
|
489
|
+
return predicate.operator === "is" && predicate.value === null;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
479
493
|
function validateOperatorValue(operator, value, field, kind, nullable) {
|
|
480
494
|
if (operator === "is" || operator === "isNot") {
|
|
481
495
|
if (value !== null) {
|
|
@@ -36,6 +36,8 @@ export declare class GrpcPackageRelationError extends CantonError {
|
|
|
36
36
|
export declare class GrpcPackageRelationReader {
|
|
37
37
|
private readonly packageService;
|
|
38
38
|
private readonly packageLoader;
|
|
39
|
+
/** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
|
|
40
|
+
private readonly resolved;
|
|
39
41
|
constructor(packageService: GrpcPackageService, packageLoader?: DamlLfPackageLoader);
|
|
40
42
|
readAllAsync(): Promise<readonly GrpcPackageMetadata[]>;
|
|
41
43
|
readPackagesAsync(packageIds: readonly string[]): Promise<readonly GrpcPackageMetadata[]>;
|
|
@@ -19,6 +19,8 @@ export class GrpcPackageRelationError extends CantonError {
|
|
|
19
19
|
export class GrpcPackageRelationReader {
|
|
20
20
|
packageService;
|
|
21
21
|
packageLoader;
|
|
22
|
+
/** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
|
|
23
|
+
resolved = new Map();
|
|
22
24
|
constructor(packageService, packageLoader = new DamlLfPackageLoader()) {
|
|
23
25
|
this.packageService = packageService;
|
|
24
26
|
this.packageLoader = packageLoader;
|
|
@@ -49,6 +51,10 @@ export class GrpcPackageRelationReader {
|
|
|
49
51
|
return Object.freeze(packages.slice().sort((left, right) => left.id.localeCompare(right.id)));
|
|
50
52
|
}
|
|
51
53
|
async readPackageAsync(packageId) {
|
|
54
|
+
const cached = this.resolved.get(packageId);
|
|
55
|
+
if (cached !== undefined) {
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
52
58
|
let response;
|
|
53
59
|
try {
|
|
54
60
|
response = await this.packageService.getPackageAsync({ packageId });
|
|
@@ -84,12 +90,14 @@ export class GrpcPackageRelationReader {
|
|
|
84
90
|
.map((template) => templateMetadata(packageId, packageName, module.name, template))),
|
|
85
91
|
...interfaceMetadata(packageId, packageName, result.rawPackage),
|
|
86
92
|
];
|
|
87
|
-
|
|
93
|
+
const metadata = Object.freeze({
|
|
88
94
|
id: packageId,
|
|
89
95
|
name: packageName,
|
|
90
96
|
version: requiredPackageText(pkg.packageVersion, "version"),
|
|
91
97
|
templates: Object.freeze(templates),
|
|
92
98
|
});
|
|
99
|
+
this.resolved.set(packageId, metadata);
|
|
100
|
+
return metadata;
|
|
93
101
|
}
|
|
94
102
|
catch (error) {
|
|
95
103
|
throw isGrpcPackageRelationError(error) ? error : new GrpcPackageRelationError(packageId, errorMessage(error));
|
|
@@ -5,7 +5,7 @@ import { createQueryDataset } from "../canonical/query-dataset.js";
|
|
|
5
5
|
import { normalizeAggregate, normalizeCount, normalizeFindMany, normalizeFindUnique, normalizeGroupBy } from "../canonical/query-normalizer.js";
|
|
6
6
|
import { queryRelationEdges, queryRelations } from "../canonical/query-schema.js";
|
|
7
7
|
import { QuerySource } from "../query-source.js";
|
|
8
|
-
import { createGrpcQueryDataset, mapGrpcQueryRelationFragment, referencedGrpcPackageIds } from "./grpc-relation-mapper.js";
|
|
8
|
+
import { contractTypeMetadataFromCreations, createGrpcQueryDataset, mapGrpcQueryRelationFragment, referencedGrpcPackageIds } from "./grpc-relation-mapper.js";
|
|
9
9
|
import { GrpcPackageRelationReader } from "./grpc-package-relation-reader.js";
|
|
10
10
|
import { GrpcQuerySnapshotReader } from "./grpc-query-snapshot-reader.js";
|
|
11
11
|
export class GrpcQueryClient {
|
|
@@ -84,14 +84,24 @@ class DefaultGrpcQueryDataProvider {
|
|
|
84
84
|
const history = await this.snapshots.readHistoryAsync(endInclusive);
|
|
85
85
|
const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
|
|
86
86
|
const fragment = mapGrpcQueryRelationFragment(transactions);
|
|
87
|
+
const useCreationsOnlyMetadata = requiresPackageMetadata(closure)
|
|
88
|
+
&& query.relation !== "packages" && query.relation !== "contractTypes" && query.relation !== "exerciseTypes"
|
|
89
|
+
&& derivableFromCreationsOnly(closure);
|
|
87
90
|
const packageMetadata = !requiresPackageMetadata(closure)
|
|
88
91
|
? []
|
|
89
92
|
: query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes"
|
|
90
93
|
? await this.packages.readAllAsync()
|
|
91
|
-
:
|
|
94
|
+
: useCreationsOnlyMetadata
|
|
95
|
+
? contractTypeMetadataFromCreations(fragment.creationIdentities)
|
|
96
|
+
: await this.packages.readPackagesAsync(referencedGrpcPackageIds(fragment));
|
|
97
|
+
// The closure guarantees this query never reads "exercises" here, but the replayed window can still
|
|
98
|
+
// contain unrelated exercises (createGrpcQueryDataset always resolves package metadata for every
|
|
99
|
+
// exercise in the fragment). contractTypeMetadataFromCreations only knows about templates, so drop
|
|
100
|
+
// the exercises this query doesn't need instead of paying for their canonical keys.
|
|
101
|
+
const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
|
|
92
102
|
return packageMetadata.length === 0
|
|
93
103
|
? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
|
|
94
|
-
: createGrpcQueryDataset(
|
|
104
|
+
: createGrpcQueryDataset(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
95
105
|
}
|
|
96
106
|
const endInclusive = cached?.activeAtOffset ?? (await this.options.stateService.getLedgerEndAsync({})).offset;
|
|
97
107
|
if (query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes") {
|
|
@@ -108,9 +118,16 @@ class DefaultGrpcQueryDataProvider {
|
|
|
108
118
|
}
|
|
109
119
|
const active = await this.snapshots.readActiveContractsAsync(endInclusive, partiesFor(query));
|
|
110
120
|
const fragment = mapGrpcQueryRelationFragment([], active.activeContracts);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
if (!requiresPackageMetadata(closure)) {
|
|
122
|
+
return fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
|
|
123
|
+
}
|
|
124
|
+
// Reaching here guarantees query.relation === "contracts" with needsHistory === false, which (per
|
|
125
|
+
// predicateRequiresHistory/includesRequireHistory above) is only possible when the closure never
|
|
126
|
+
// touches "exercises"/"exerciseTypes"/"packages"/"transactions"/"events" — those unconditionally force
|
|
127
|
+
// history. So requiresPackageMetadata(closure) here can only be "contractTypes", and every contract in
|
|
128
|
+
// this ACS-only fragment already carries its own packageName — no Package Service call needed.
|
|
129
|
+
const packageMetadata = contractTypeMetadataFromCreations(fragment.creationIdentities);
|
|
130
|
+
return createGrpcQueryDataset(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
function requiresHistory(query) {
|
|
@@ -139,7 +156,15 @@ function predicateProvesActive(predicate) {
|
|
|
139
156
|
return false;
|
|
140
157
|
}
|
|
141
158
|
else if (predicate.kind === "scalar") {
|
|
142
|
-
|
|
159
|
+
if (predicate.path.length !== 1) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const [field] = predicate.path;
|
|
163
|
+
// A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the
|
|
164
|
+
// same fact as active: true (kept in sync with provesActive in canonical/query-normalizer.ts).
|
|
165
|
+
return field === "active"
|
|
166
|
+
? predicate.operator === "equals" && predicate.value === true
|
|
167
|
+
: (field === "archivedAt" || field === "archivedEventOffset") && predicate.operator === "is" && predicate.value === null;
|
|
143
168
|
}
|
|
144
169
|
else if (predicate.kind === "and") {
|
|
145
170
|
return predicate.children.some(predicateProvesActive);
|
|
@@ -152,6 +177,24 @@ function predicateProvesActive(predicate) {
|
|
|
152
177
|
function requiresPackageMetadata(closure) {
|
|
153
178
|
return closure.has("packages") || closure.has("contractTypes") || closure.has("exercises") || closure.has("exerciseTypes");
|
|
154
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* True when contractType metadata can be derived from the fetched creation events alone (see
|
|
182
|
+
* contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
|
|
183
|
+
* own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
|
|
184
|
+
* "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
|
|
185
|
+
* "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
|
|
186
|
+
* the template side — so it is not a reliable source for the choice owner's package name when a choice was
|
|
187
|
+
* exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
|
|
188
|
+
* equivalent at all.
|
|
189
|
+
*
|
|
190
|
+
* The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
|
|
191
|
+
* it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
|
|
192
|
+
* this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
|
|
193
|
+
* every exercise present — this function only knows about templates, not choices.
|
|
194
|
+
*/
|
|
195
|
+
function derivableFromCreationsOnly(closure) {
|
|
196
|
+
return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
|
|
197
|
+
}
|
|
155
198
|
function predicateRequiresHistory(relation, predicate) {
|
|
156
199
|
if (predicate === undefined || predicate.kind === "scalar") {
|
|
157
200
|
return false;
|
|
@@ -28,6 +28,7 @@ export interface GrpcQueryCreationIdentity {
|
|
|
28
28
|
}>;
|
|
29
29
|
readonly creationPackageId: string;
|
|
30
30
|
readonly representativePackageId: string | null;
|
|
31
|
+
readonly packageName: string;
|
|
31
32
|
readonly payload: unknown;
|
|
32
33
|
readonly witnesses: readonly string[];
|
|
33
34
|
readonly createdAt: Date;
|
|
@@ -65,3 +66,11 @@ export declare function mapGrpcQueryRelationFragment(source: readonly Transactio
|
|
|
65
66
|
export declare function createGrpcQueryDataset(fragment: GrpcQueryRelationFragment, packages: readonly GrpcPackageMetadata[], endInclusive: string, instanceId: string): QueryDataset;
|
|
66
67
|
/** Package payloads required for a contract/history relation plan, excluding creation-only provenance. */
|
|
67
68
|
export declare function referencedGrpcPackageIds(fragment: GrpcQueryRelationFragment): readonly string[];
|
|
69
|
+
/**
|
|
70
|
+
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
71
|
+
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
72
|
+
* {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
|
|
73
|
+
* so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
|
|
74
|
+
* can see the "packages" relation, so it is never observed.
|
|
75
|
+
*/
|
|
76
|
+
export declare function contractTypeMetadataFromCreations(creationIdentities: readonly GrpcQueryCreationIdentity[]): readonly GrpcPackageMetadata[];
|
|
@@ -238,6 +238,46 @@ export function referencedGrpcPackageIds(fragment) {
|
|
|
238
238
|
...fragment.typeIdentities.filter((identity) => identity.choice !== undefined).map((identity) => identity.packageId),
|
|
239
239
|
])].sort());
|
|
240
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
243
|
+
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
244
|
+
* {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
|
|
245
|
+
* so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
|
|
246
|
+
* can see the "packages" relation, so it is never observed.
|
|
247
|
+
*/
|
|
248
|
+
export function contractTypeMetadataFromCreations(creationIdentities) {
|
|
249
|
+
const packages = new Map();
|
|
250
|
+
for (const creation of creationIdentities) {
|
|
251
|
+
const packageId = creation.representativePackageId ?? creation.creationPackageId;
|
|
252
|
+
const { moduleName, entityName } = creation.templateId;
|
|
253
|
+
let pkg = packages.get(packageId);
|
|
254
|
+
if (pkg === undefined) {
|
|
255
|
+
pkg = { name: creation.packageName, templates: new Map() };
|
|
256
|
+
packages.set(packageId, pkg);
|
|
257
|
+
}
|
|
258
|
+
else if (pkg.name !== creation.packageName) {
|
|
259
|
+
throw new ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
|
|
260
|
+
}
|
|
261
|
+
const identity = `${moduleName}${entityName}`;
|
|
262
|
+
if (!pkg.templates.has(identity)) {
|
|
263
|
+
const templateFqn = `${pkg.name}:${moduleName}:${entityName}`;
|
|
264
|
+
pkg.templates.set(identity, Object.freeze({
|
|
265
|
+
moduleName,
|
|
266
|
+
entityName,
|
|
267
|
+
payloadType: "template",
|
|
268
|
+
aliases: Object.freeze([templateFqn, `${moduleName}:${entityName}`, entityName]),
|
|
269
|
+
templateFqn,
|
|
270
|
+
choices: Object.freeze([]),
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
|
|
275
|
+
id,
|
|
276
|
+
name: pkg.name,
|
|
277
|
+
version: "unresolved",
|
|
278
|
+
templates: Object.freeze([...pkg.templates.values()]),
|
|
279
|
+
})));
|
|
280
|
+
}
|
|
241
281
|
function compareTemplateMetadata(left, right) {
|
|
242
282
|
return left.package.id.localeCompare(right.package.id) || left.template.moduleName.localeCompare(right.template.moduleName) || left.template.entityName.localeCompare(right.template.entityName);
|
|
243
283
|
}
|
|
@@ -617,6 +657,7 @@ function creationDescriptor(event, offset) {
|
|
|
617
657
|
templateId: copyTemplate(template),
|
|
618
658
|
creationPackageId: template.packageId,
|
|
619
659
|
representativePackageId: nullableString(event.representativePackageId),
|
|
660
|
+
packageName: event.packageName,
|
|
620
661
|
payload: mapGrpcQueryValue({ sum: { oneofKind: "record", record: event.createArguments } }),
|
|
621
662
|
witnesses: immutableStrings(event.witnessParties, "created event witnesses", true),
|
|
622
663
|
createdAt,
|