@distrohelena/canton-typescript-sdk 0.1.44 → 0.1.46
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/grpc/grpc-query-client.js +21 -30
- package/dist/cjs/query/grpc/grpc-relation-mapper.js +92 -34
- package/dist/query/grpc/grpc-query-client.js +22 -31
- package/dist/query/grpc/grpc-relation-mapper.d.ts +18 -2
- package/dist/query/grpc/grpc-relation-mapper.js +91 -34
- package/package.json +1 -1
|
@@ -95,21 +95,30 @@ class DefaultGrpcQueryDataProvider {
|
|
|
95
95
|
const history = await this.snapshots.readHistoryAsync(endInclusive);
|
|
96
96
|
const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
|
|
97
97
|
const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)(transactions);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
// Catalog queries rooted at packages/contractTypes/exerciseTypes need every known package's
|
|
99
|
+
// full type inventory, which only the decoded archives provide.
|
|
100
|
+
const catalogRelation = query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes";
|
|
101
|
+
const closureNeedsExercises = closure.has("exercises") || closure.has("exerciseTypes");
|
|
102
|
+
// Non-catalog metadata can usually come straight from the fetched events: creations always name
|
|
103
|
+
// their own package, and direct exercises name their choice owner's. packageMetadataFromEvents
|
|
104
|
+
// returns undefined when the window holds an interface-exercised choice (owner package name is
|
|
105
|
+
// not on the event), and "packages" rows (version) have no event equivalent at all — both fall
|
|
106
|
+
// back to the archive decode.
|
|
107
|
+
const derivedMetadata = requiresPackageMetadata(closure) && !catalogRelation && !closure.has("packages")
|
|
108
|
+
? closureNeedsExercises
|
|
109
|
+
? (0, grpc_relation_mapper_js_1.packageMetadataFromEvents)(fragment)
|
|
110
|
+
: (0, grpc_relation_mapper_js_1.contractTypeMetadataFromCreations)(fragment.creationIdentities)
|
|
111
|
+
: undefined;
|
|
101
112
|
const packageMetadata = !requiresPackageMetadata(closure)
|
|
102
113
|
? []
|
|
103
|
-
:
|
|
114
|
+
: catalogRelation
|
|
104
115
|
? await this.packages.readAllAsync()
|
|
105
|
-
:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
// the exercises this query doesn't need instead of paying for their canonical keys.
|
|
112
|
-
const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
|
|
116
|
+
: derivedMetadata ?? await this.packages.readPackagesAsync((0, grpc_relation_mapper_js_1.referencedGrpcPackageIds)(fragment));
|
|
117
|
+
// When the closure never reads exercises, the replayed window's unrelated exercises are dropped:
|
|
118
|
+
// createGrpcQueryDataset resolves canonical keys for every exercise present, and creations-only
|
|
119
|
+
// metadata knows nothing about choices. When the closure does read exercises, the event-derived
|
|
120
|
+
// metadata covers them and they stay.
|
|
121
|
+
const datasetFragment = derivedMetadata !== undefined && !closureNeedsExercises ? { ...fragment, exercises: [] } : fragment;
|
|
113
122
|
return packageMetadata.length === 0
|
|
114
123
|
? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
|
|
115
124
|
: (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
@@ -292,24 +301,6 @@ function isValidTemplateRef(ref) {
|
|
|
292
301
|
return false;
|
|
293
302
|
}
|
|
294
303
|
}
|
|
295
|
-
/**
|
|
296
|
-
* True when contractType metadata can be derived from the fetched creation events alone (see
|
|
297
|
-
* contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
|
|
298
|
-
* own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
|
|
299
|
-
* "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
|
|
300
|
-
* "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
|
|
301
|
-
* the template side — so it is not a reliable source for the choice owner's package name when a choice was
|
|
302
|
-
* exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
|
|
303
|
-
* equivalent at all.
|
|
304
|
-
*
|
|
305
|
-
* The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
|
|
306
|
-
* it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
|
|
307
|
-
* this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
|
|
308
|
-
* every exercise present — this function only knows about templates, not choices.
|
|
309
|
-
*/
|
|
310
|
-
function derivableFromCreationsOnly(closure) {
|
|
311
|
-
return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
|
|
312
|
-
}
|
|
313
304
|
function predicateRequiresHistory(relation, predicate) {
|
|
314
305
|
if (predicate === undefined || predicate.kind === "scalar") {
|
|
315
306
|
return false;
|
|
@@ -4,6 +4,7 @@ exports.mapGrpcQueryRelationFragment = mapGrpcQueryRelationFragment;
|
|
|
4
4
|
exports.createGrpcQueryDataset = createGrpcQueryDataset;
|
|
5
5
|
exports.referencedGrpcPackageIds = referencedGrpcPackageIds;
|
|
6
6
|
exports.contractTypeMetadataFromCreations = contractTypeMetadataFromCreations;
|
|
7
|
+
exports.packageMetadataFromEvents = packageMetadataFromEvents;
|
|
7
8
|
const validation_error_js_1 = require("../../core/errors/validation-error.js");
|
|
8
9
|
const query_dataset_js_1 = require("../canonical/query-dataset.js");
|
|
9
10
|
const public_identity_js_1 = require("../canonical/public-identity.js");
|
|
@@ -248,40 +249,92 @@ function referencedGrpcPackageIds(fragment) {
|
|
|
248
249
|
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
249
250
|
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
250
251
|
* {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
|
|
252
|
-
* can see the "packages" relation, so it is never observed.
|
|
252
|
+
* so no archive decode is needed. "version" is a per-package placeholder: this path is unreachable from
|
|
253
|
+
* any query that can see the "packages" relation, so it is never observed.
|
|
253
254
|
*/
|
|
254
255
|
function contractTypeMetadataFromCreations(creationIdentities) {
|
|
255
256
|
const packages = new Map();
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
257
|
+
addDerivedCreationEntries(packages, creationIdentities);
|
|
258
|
+
return finalizeDerivedPackages(packages);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Extends the creations-only derivation to exercises: for a direct exercise the choice owner IS the
|
|
262
|
+
* exercised template, so the event's own packageName names the owner's package and choice/consuming come
|
|
263
|
+
* straight off the event. Returns undefined when the window contains an interface-exercised choice — its
|
|
264
|
+
* owner is the interface, whose package name the event does not carry, so only the decoded archive can
|
|
265
|
+
* produce its canonical choiceFqn. Unobserved choices are simply absent, which is complete for query plans
|
|
266
|
+
* that reach exerciseTypes only through observed exercises (never for exerciseTypes catalog queries).
|
|
267
|
+
*/
|
|
268
|
+
function packageMetadataFromEvents(fragment) {
|
|
269
|
+
if (fragment.typeIdentities.some((identity) => identity.choice !== undefined && identity.packageName === undefined)) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
const packages = new Map();
|
|
273
|
+
addDerivedCreationEntries(packages, fragment.creationIdentities);
|
|
274
|
+
for (const identity of fragment.typeIdentities) {
|
|
275
|
+
if (identity.packageName === undefined) {
|
|
276
|
+
throw new validation_error_js_1.ValidationError("gRPC query contract type identity is missing its package name");
|
|
277
|
+
}
|
|
278
|
+
const template = derivedTemplateFor(derivedPackageFor(packages, identity.packageId, identity.packageName), identity.templateId.moduleName, identity.templateId.entityName);
|
|
279
|
+
if (identity.choice !== undefined) {
|
|
280
|
+
const consuming = template.choices.get(identity.choice);
|
|
281
|
+
if (consuming !== undefined && consuming !== identity.consuming) {
|
|
282
|
+
throw new validation_error_js_1.ValidationError(`gRPC query choice ${identity.choice} reports conflicting consuming flags`);
|
|
283
|
+
}
|
|
284
|
+
template.choices.set(identity.choice, identity.consuming === true);
|
|
278
285
|
}
|
|
279
286
|
}
|
|
287
|
+
return finalizeDerivedPackages(packages);
|
|
288
|
+
}
|
|
289
|
+
function addDerivedCreationEntries(packages, creationIdentities) {
|
|
290
|
+
for (const creation of creationIdentities) {
|
|
291
|
+
derivedTemplateFor(derivedPackageFor(packages, creation.representativePackageId ?? creation.creationPackageId, creation.packageName), creation.templateId.moduleName, creation.templateId.entityName);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function derivedPackageFor(packages, packageId, packageName) {
|
|
295
|
+
const existing = packages.get(packageId);
|
|
296
|
+
if (existing === undefined) {
|
|
297
|
+
const created = { name: packageName, templates: new Map() };
|
|
298
|
+
packages.set(packageId, created);
|
|
299
|
+
return created;
|
|
300
|
+
}
|
|
301
|
+
else if (existing.name !== packageName) {
|
|
302
|
+
throw new validation_error_js_1.ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
|
|
303
|
+
}
|
|
304
|
+
return existing;
|
|
305
|
+
}
|
|
306
|
+
function derivedTemplateFor(pkg, moduleName, entityName) {
|
|
307
|
+
const identity = `${moduleName} ${entityName}`;
|
|
308
|
+
let template = pkg.templates.get(identity);
|
|
309
|
+
if (template === undefined) {
|
|
310
|
+
template = { moduleName, entityName, choices: new Map() };
|
|
311
|
+
pkg.templates.set(identity, template);
|
|
312
|
+
}
|
|
313
|
+
return template;
|
|
314
|
+
}
|
|
315
|
+
function finalizeDerivedPackages(packages) {
|
|
280
316
|
return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
|
|
281
317
|
id,
|
|
282
318
|
name: pkg.name,
|
|
283
|
-
|
|
284
|
-
|
|
319
|
+
// Unique per package: two versions of the same package name can both appear in one window, and
|
|
320
|
+
// duplicate name+version pairs are rejected during dataset normalization.
|
|
321
|
+
version: `unresolved-${id}`,
|
|
322
|
+
templates: Object.freeze([...pkg.templates.values()].map((template) => {
|
|
323
|
+
const templateFqn = `${pkg.name}:${template.moduleName}:${template.entityName}`;
|
|
324
|
+
return Object.freeze({
|
|
325
|
+
moduleName: template.moduleName,
|
|
326
|
+
entityName: template.entityName,
|
|
327
|
+
payloadType: "template",
|
|
328
|
+
aliases: Object.freeze([templateFqn, `${template.moduleName}:${template.entityName}`, template.entityName]),
|
|
329
|
+
templateFqn,
|
|
330
|
+
choices: Object.freeze([...template.choices.entries()].map(([choice, consuming]) => Object.freeze({
|
|
331
|
+
choice,
|
|
332
|
+
consuming,
|
|
333
|
+
aliases: Object.freeze([`${templateFqn}:${choice}`, `${template.moduleName}:${template.entityName}:${choice}`, `${template.entityName}:${choice}`, choice]),
|
|
334
|
+
choiceFqn: `${templateFqn}:${choice}`,
|
|
335
|
+
})).sort((left, right) => left.choice.localeCompare(right.choice))),
|
|
336
|
+
});
|
|
337
|
+
})),
|
|
285
338
|
})));
|
|
286
339
|
}
|
|
287
340
|
function compareTemplateMetadata(left, right) {
|
|
@@ -304,7 +357,6 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
|
|
|
304
357
|
}
|
|
305
358
|
const packageValues = Array.from(packages);
|
|
306
359
|
const packageIds = new Set();
|
|
307
|
-
const packageNameVersions = new Set();
|
|
308
360
|
const normalized = [];
|
|
309
361
|
for (const pkg of packageValues) {
|
|
310
362
|
if (pkg === null || typeof pkg !== "object") {
|
|
@@ -319,11 +371,10 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
|
|
|
319
371
|
throw new validation_error_js_1.ValidationError(`gRPC query has duplicate package metadata ${packageId}`);
|
|
320
372
|
}
|
|
321
373
|
packageIds.add(packageId);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
packageNameVersions.add(nameVersion);
|
|
374
|
+
// Same name+version under two different ids is NOT rejected: package ids are content-addressed, so a
|
|
375
|
+
// recompiled or re-uploaded DAR legitimately reappears with a new id and an unchanged manifest. The
|
|
376
|
+
// name-derived canonical rows go through deduplicateCanonicalRows, which throws on any genuine
|
|
377
|
+
// metadata conflict for one canonical key — the only conflict that would actually matter.
|
|
327
378
|
const templatesValue = value.templates;
|
|
328
379
|
if (!Array.isArray(templatesValue)) {
|
|
329
380
|
throw new validation_error_js_1.ValidationError(`gRPC query package ${packageId} templates are invalid`);
|
|
@@ -569,12 +620,19 @@ function identitiesFor(event) {
|
|
|
569
620
|
}
|
|
570
621
|
function typeIdentityRows(event, registry) {
|
|
571
622
|
const template = requiredTemplate(event.templateId, "event template");
|
|
572
|
-
const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId };
|
|
623
|
+
const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId, packageName: event.packageName };
|
|
573
624
|
if (!isExercised(event)) {
|
|
574
625
|
return [contract];
|
|
575
626
|
}
|
|
576
627
|
const owner = exerciseOwner(event);
|
|
577
|
-
return [contract, {
|
|
628
|
+
return [contract, {
|
|
629
|
+
pk: registry.get(exerciseIdentity(owner, event.choice, event.consuming)),
|
|
630
|
+
templateId: copyTemplate(owner),
|
|
631
|
+
packageId: owner.packageId,
|
|
632
|
+
...(event.interfaceId === undefined ? { packageName: event.packageName } : {}),
|
|
633
|
+
choice: event.choice,
|
|
634
|
+
consuming: event.consuming,
|
|
635
|
+
}];
|
|
578
636
|
}
|
|
579
637
|
function activeContractEntries(responses) {
|
|
580
638
|
const entries = responses.map((response) => {
|
|
@@ -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 { contractTypeMetadataFromCreations, createGrpcQueryDataset, mapGrpcQueryRelationFragment, referencedGrpcPackageIds } from "./grpc-relation-mapper.js";
|
|
8
|
+
import { contractTypeMetadataFromCreations, createGrpcQueryDataset, mapGrpcQueryRelationFragment, packageMetadataFromEvents, 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
|
import { validDottedNameString } from "./grpc-query-value-mapper.js";
|
|
@@ -91,21 +91,30 @@ class DefaultGrpcQueryDataProvider {
|
|
|
91
91
|
const history = await this.snapshots.readHistoryAsync(endInclusive);
|
|
92
92
|
const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
|
|
93
93
|
const fragment = mapGrpcQueryRelationFragment(transactions);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// Catalog queries rooted at packages/contractTypes/exerciseTypes need every known package's
|
|
95
|
+
// full type inventory, which only the decoded archives provide.
|
|
96
|
+
const catalogRelation = query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes";
|
|
97
|
+
const closureNeedsExercises = closure.has("exercises") || closure.has("exerciseTypes");
|
|
98
|
+
// Non-catalog metadata can usually come straight from the fetched events: creations always name
|
|
99
|
+
// their own package, and direct exercises name their choice owner's. packageMetadataFromEvents
|
|
100
|
+
// returns undefined when the window holds an interface-exercised choice (owner package name is
|
|
101
|
+
// not on the event), and "packages" rows (version) have no event equivalent at all — both fall
|
|
102
|
+
// back to the archive decode.
|
|
103
|
+
const derivedMetadata = requiresPackageMetadata(closure) && !catalogRelation && !closure.has("packages")
|
|
104
|
+
? closureNeedsExercises
|
|
105
|
+
? packageMetadataFromEvents(fragment)
|
|
106
|
+
: contractTypeMetadataFromCreations(fragment.creationIdentities)
|
|
107
|
+
: undefined;
|
|
97
108
|
const packageMetadata = !requiresPackageMetadata(closure)
|
|
98
109
|
? []
|
|
99
|
-
:
|
|
110
|
+
: catalogRelation
|
|
100
111
|
? await this.packages.readAllAsync()
|
|
101
|
-
:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
// the exercises this query doesn't need instead of paying for their canonical keys.
|
|
108
|
-
const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
|
|
112
|
+
: derivedMetadata ?? await this.packages.readPackagesAsync(referencedGrpcPackageIds(fragment));
|
|
113
|
+
// When the closure never reads exercises, the replayed window's unrelated exercises are dropped:
|
|
114
|
+
// createGrpcQueryDataset resolves canonical keys for every exercise present, and creations-only
|
|
115
|
+
// metadata knows nothing about choices. When the closure does read exercises, the event-derived
|
|
116
|
+
// metadata covers them and they stay.
|
|
117
|
+
const datasetFragment = derivedMetadata !== undefined && !closureNeedsExercises ? { ...fragment, exercises: [] } : fragment;
|
|
109
118
|
return packageMetadata.length === 0
|
|
110
119
|
? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
|
|
111
120
|
: createGrpcQueryDataset(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
|
|
@@ -288,24 +297,6 @@ function isValidTemplateRef(ref) {
|
|
|
288
297
|
return false;
|
|
289
298
|
}
|
|
290
299
|
}
|
|
291
|
-
/**
|
|
292
|
-
* True when contractType metadata can be derived from the fetched creation events alone (see
|
|
293
|
-
* contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
|
|
294
|
-
* own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
|
|
295
|
-
* "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
|
|
296
|
-
* "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
|
|
297
|
-
* the template side — so it is not a reliable source for the choice owner's package name when a choice was
|
|
298
|
-
* exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
|
|
299
|
-
* equivalent at all.
|
|
300
|
-
*
|
|
301
|
-
* The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
|
|
302
|
-
* it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
|
|
303
|
-
* this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
|
|
304
|
-
* every exercise present — this function only knows about templates, not choices.
|
|
305
|
-
*/
|
|
306
|
-
function derivableFromCreationsOnly(closure) {
|
|
307
|
-
return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
|
|
308
|
-
}
|
|
309
300
|
function predicateRequiresHistory(relation, predicate) {
|
|
310
301
|
if (predicate === undefined || predicate.kind === "scalar") {
|
|
311
302
|
return false;
|
|
@@ -11,6 +11,13 @@ export interface GrpcQueryTypeIdentity {
|
|
|
11
11
|
entityName: string;
|
|
12
12
|
}>;
|
|
13
13
|
readonly packageId: string;
|
|
14
|
+
/**
|
|
15
|
+
* The owning package's name when the event states it reliably: always for contract types
|
|
16
|
+
* (CreatedEvent/ExercisedEvent packageName is the contract's package), and for choice types only on
|
|
17
|
+
* direct exercises — an interface-exercised choice is owned by the interface, whose package name the
|
|
18
|
+
* event does not carry.
|
|
19
|
+
*/
|
|
20
|
+
readonly packageName?: string;
|
|
14
21
|
readonly choice?: string;
|
|
15
22
|
readonly consuming?: boolean;
|
|
16
23
|
}
|
|
@@ -70,7 +77,16 @@ export declare function referencedGrpcPackageIds(fragment: GrpcQueryRelationFrag
|
|
|
70
77
|
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
71
78
|
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
72
79
|
* {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
|
|
74
|
-
* can see the "packages" relation, so it is never observed.
|
|
80
|
+
* so no archive decode is needed. "version" is a per-package placeholder: this path is unreachable from
|
|
81
|
+
* any query that can see the "packages" relation, so it is never observed.
|
|
75
82
|
*/
|
|
76
83
|
export declare function contractTypeMetadataFromCreations(creationIdentities: readonly GrpcQueryCreationIdentity[]): readonly GrpcPackageMetadata[];
|
|
84
|
+
/**
|
|
85
|
+
* Extends the creations-only derivation to exercises: for a direct exercise the choice owner IS the
|
|
86
|
+
* exercised template, so the event's own packageName names the owner's package and choice/consuming come
|
|
87
|
+
* straight off the event. Returns undefined when the window contains an interface-exercised choice — its
|
|
88
|
+
* owner is the interface, whose package name the event does not carry, so only the decoded archive can
|
|
89
|
+
* produce its canonical choiceFqn. Unobserved choices are simply absent, which is complete for query plans
|
|
90
|
+
* that reach exerciseTypes only through observed exercises (never for exerciseTypes catalog queries).
|
|
91
|
+
*/
|
|
92
|
+
export declare function packageMetadataFromEvents(fragment: Pick<GrpcQueryRelationFragment, "creationIdentities" | "typeIdentities">): readonly GrpcPackageMetadata[] | undefined;
|
|
@@ -242,40 +242,92 @@ export function referencedGrpcPackageIds(fragment) {
|
|
|
242
242
|
* Derives contractType metadata straight from already-fetched created-contract events instead of the
|
|
243
243
|
* Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
|
|
244
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
|
|
246
|
-
* can see the "packages" relation, so it is never observed.
|
|
245
|
+
* so no archive decode is needed. "version" is a per-package placeholder: this path is unreachable from
|
|
246
|
+
* any query that can see the "packages" relation, so it is never observed.
|
|
247
247
|
*/
|
|
248
248
|
export function contractTypeMetadataFromCreations(creationIdentities) {
|
|
249
249
|
const packages = new Map();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
250
|
+
addDerivedCreationEntries(packages, creationIdentities);
|
|
251
|
+
return finalizeDerivedPackages(packages);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Extends the creations-only derivation to exercises: for a direct exercise the choice owner IS the
|
|
255
|
+
* exercised template, so the event's own packageName names the owner's package and choice/consuming come
|
|
256
|
+
* straight off the event. Returns undefined when the window contains an interface-exercised choice — its
|
|
257
|
+
* owner is the interface, whose package name the event does not carry, so only the decoded archive can
|
|
258
|
+
* produce its canonical choiceFqn. Unobserved choices are simply absent, which is complete for query plans
|
|
259
|
+
* that reach exerciseTypes only through observed exercises (never for exerciseTypes catalog queries).
|
|
260
|
+
*/
|
|
261
|
+
export function packageMetadataFromEvents(fragment) {
|
|
262
|
+
if (fragment.typeIdentities.some((identity) => identity.choice !== undefined && identity.packageName === undefined)) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
const packages = new Map();
|
|
266
|
+
addDerivedCreationEntries(packages, fragment.creationIdentities);
|
|
267
|
+
for (const identity of fragment.typeIdentities) {
|
|
268
|
+
if (identity.packageName === undefined) {
|
|
269
|
+
throw new ValidationError("gRPC query contract type identity is missing its package name");
|
|
270
|
+
}
|
|
271
|
+
const template = derivedTemplateFor(derivedPackageFor(packages, identity.packageId, identity.packageName), identity.templateId.moduleName, identity.templateId.entityName);
|
|
272
|
+
if (identity.choice !== undefined) {
|
|
273
|
+
const consuming = template.choices.get(identity.choice);
|
|
274
|
+
if (consuming !== undefined && consuming !== identity.consuming) {
|
|
275
|
+
throw new ValidationError(`gRPC query choice ${identity.choice} reports conflicting consuming flags`);
|
|
276
|
+
}
|
|
277
|
+
template.choices.set(identity.choice, identity.consuming === true);
|
|
272
278
|
}
|
|
273
279
|
}
|
|
280
|
+
return finalizeDerivedPackages(packages);
|
|
281
|
+
}
|
|
282
|
+
function addDerivedCreationEntries(packages, creationIdentities) {
|
|
283
|
+
for (const creation of creationIdentities) {
|
|
284
|
+
derivedTemplateFor(derivedPackageFor(packages, creation.representativePackageId ?? creation.creationPackageId, creation.packageName), creation.templateId.moduleName, creation.templateId.entityName);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function derivedPackageFor(packages, packageId, packageName) {
|
|
288
|
+
const existing = packages.get(packageId);
|
|
289
|
+
if (existing === undefined) {
|
|
290
|
+
const created = { name: packageName, templates: new Map() };
|
|
291
|
+
packages.set(packageId, created);
|
|
292
|
+
return created;
|
|
293
|
+
}
|
|
294
|
+
else if (existing.name !== packageName) {
|
|
295
|
+
throw new ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
|
|
296
|
+
}
|
|
297
|
+
return existing;
|
|
298
|
+
}
|
|
299
|
+
function derivedTemplateFor(pkg, moduleName, entityName) {
|
|
300
|
+
const identity = `${moduleName} ${entityName}`;
|
|
301
|
+
let template = pkg.templates.get(identity);
|
|
302
|
+
if (template === undefined) {
|
|
303
|
+
template = { moduleName, entityName, choices: new Map() };
|
|
304
|
+
pkg.templates.set(identity, template);
|
|
305
|
+
}
|
|
306
|
+
return template;
|
|
307
|
+
}
|
|
308
|
+
function finalizeDerivedPackages(packages) {
|
|
274
309
|
return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
|
|
275
310
|
id,
|
|
276
311
|
name: pkg.name,
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
// Unique per package: two versions of the same package name can both appear in one window, and
|
|
313
|
+
// duplicate name+version pairs are rejected during dataset normalization.
|
|
314
|
+
version: `unresolved-${id}`,
|
|
315
|
+
templates: Object.freeze([...pkg.templates.values()].map((template) => {
|
|
316
|
+
const templateFqn = `${pkg.name}:${template.moduleName}:${template.entityName}`;
|
|
317
|
+
return Object.freeze({
|
|
318
|
+
moduleName: template.moduleName,
|
|
319
|
+
entityName: template.entityName,
|
|
320
|
+
payloadType: "template",
|
|
321
|
+
aliases: Object.freeze([templateFqn, `${template.moduleName}:${template.entityName}`, template.entityName]),
|
|
322
|
+
templateFqn,
|
|
323
|
+
choices: Object.freeze([...template.choices.entries()].map(([choice, consuming]) => Object.freeze({
|
|
324
|
+
choice,
|
|
325
|
+
consuming,
|
|
326
|
+
aliases: Object.freeze([`${templateFqn}:${choice}`, `${template.moduleName}:${template.entityName}:${choice}`, `${template.entityName}:${choice}`, choice]),
|
|
327
|
+
choiceFqn: `${templateFqn}:${choice}`,
|
|
328
|
+
})).sort((left, right) => left.choice.localeCompare(right.choice))),
|
|
329
|
+
});
|
|
330
|
+
})),
|
|
279
331
|
})));
|
|
280
332
|
}
|
|
281
333
|
function compareTemplateMetadata(left, right) {
|
|
@@ -298,7 +350,6 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
|
|
|
298
350
|
}
|
|
299
351
|
const packageValues = Array.from(packages);
|
|
300
352
|
const packageIds = new Set();
|
|
301
|
-
const packageNameVersions = new Set();
|
|
302
353
|
const normalized = [];
|
|
303
354
|
for (const pkg of packageValues) {
|
|
304
355
|
if (pkg === null || typeof pkg !== "object") {
|
|
@@ -313,11 +364,10 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
|
|
|
313
364
|
throw new ValidationError(`gRPC query has duplicate package metadata ${packageId}`);
|
|
314
365
|
}
|
|
315
366
|
packageIds.add(packageId);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
packageNameVersions.add(nameVersion);
|
|
367
|
+
// Same name+version under two different ids is NOT rejected: package ids are content-addressed, so a
|
|
368
|
+
// recompiled or re-uploaded DAR legitimately reappears with a new id and an unchanged manifest. The
|
|
369
|
+
// name-derived canonical rows go through deduplicateCanonicalRows, which throws on any genuine
|
|
370
|
+
// metadata conflict for one canonical key — the only conflict that would actually matter.
|
|
321
371
|
const templatesValue = value.templates;
|
|
322
372
|
if (!Array.isArray(templatesValue)) {
|
|
323
373
|
throw new ValidationError(`gRPC query package ${packageId} templates are invalid`);
|
|
@@ -563,12 +613,19 @@ function identitiesFor(event) {
|
|
|
563
613
|
}
|
|
564
614
|
function typeIdentityRows(event, registry) {
|
|
565
615
|
const template = requiredTemplate(event.templateId, "event template");
|
|
566
|
-
const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId };
|
|
616
|
+
const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId, packageName: event.packageName };
|
|
567
617
|
if (!isExercised(event)) {
|
|
568
618
|
return [contract];
|
|
569
619
|
}
|
|
570
620
|
const owner = exerciseOwner(event);
|
|
571
|
-
return [contract, {
|
|
621
|
+
return [contract, {
|
|
622
|
+
pk: registry.get(exerciseIdentity(owner, event.choice, event.consuming)),
|
|
623
|
+
templateId: copyTemplate(owner),
|
|
624
|
+
packageId: owner.packageId,
|
|
625
|
+
...(event.interfaceId === undefined ? { packageName: event.packageName } : {}),
|
|
626
|
+
choice: event.choice,
|
|
627
|
+
consuming: event.consuming,
|
|
628
|
+
}];
|
|
572
629
|
}
|
|
573
630
|
function activeContractEntries(responses) {
|
|
574
631
|
const entries = responses.map((response) => {
|