@honua/sdk-esri-compat 0.1.17 → 0.1.18
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/contract/source.d.ts.map +1 -1
- package/contract/source.js +192 -7
- package/package.json +2 -2
package/contract/source.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/contract/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,OAAO,EAIL,KAAK,mBAAmB,EAOzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,yBAAyB,EAE1B,MAAM,qBAAqB,CAAC;AAe7B,OAAO,KAAK,EAAE,mBAAmB,EAAsB,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAsB,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAoB9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAgB/G,OAAO,EAYL,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EAQZ,KAAK,QAAQ,EAOb,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EAEd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAEpE;AAED,iFAAiF;AACjF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oBAAoB,EAC7B,KAAK,GAAE;IACL,QAAQ,CAAC,mBAAmB,CAAC,EAAE,WAAW,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;CAC5E,GACL,OAAO,CAwDT;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,gBAAgB,GACvB,qBAAqB,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/contract/source.ts"],"names":[],"mappings":"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"}
|
package/contract/source.js
CHANGED
|
@@ -230,7 +230,10 @@ export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
|
230
230
|
// set rather than silently inheriting REST-only capabilities (`pbf`,
|
|
231
231
|
// `sql`, `attachments`, `queryRelated`) the gRPC FeatureService RPC
|
|
232
232
|
// surface does not support.
|
|
233
|
-
const
|
|
233
|
+
const declaredCaps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES[descriptor.protocol];
|
|
234
|
+
const caps = client.isGrpcWeb
|
|
235
|
+
? new Set([...declaredCaps].filter((capability) => PROTOCOL_DEFAULT_CAPABILITIES.grpc.has(capability)))
|
|
236
|
+
: declaredCaps;
|
|
234
237
|
const adapterRegistry = {
|
|
235
238
|
"geoservices-feature-service": layer,
|
|
236
239
|
};
|
|
@@ -258,6 +261,13 @@ export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
|
258
261
|
};
|
|
259
262
|
},
|
|
260
263
|
async queryAggregate(request) {
|
|
264
|
+
if (client.isGrpcWeb) {
|
|
265
|
+
if (policy === "strict") {
|
|
266
|
+
throw new HonuaCapabilityNotSupportedError("queryAggregate", "grpc", descriptor.id);
|
|
267
|
+
}
|
|
268
|
+
ensureCapability(descriptor, caps, "query");
|
|
269
|
+
return grpcMaterializedAggregate(layer, request, descriptor);
|
|
270
|
+
}
|
|
261
271
|
ensureCapability(descriptor, caps, "queryAggregate");
|
|
262
272
|
const requestParams = toFeatureLayerRequest(request, descriptor);
|
|
263
273
|
const { aggregateAlias, ...aggFields } = buildAggregationFields(request.aggregation);
|
|
@@ -266,8 +276,13 @@ export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
|
266
276
|
},
|
|
267
277
|
async queryExtent(request) {
|
|
268
278
|
ensureCapability(descriptor, caps, "queryExtent");
|
|
269
|
-
const
|
|
270
|
-
|
|
279
|
+
const params = toExtentOnlyRequest(toFeatureLayerRequest(request, descriptor));
|
|
280
|
+
const response = await layer.queryFeatures(params);
|
|
281
|
+
const envelope = extractExtentEnvelope(response);
|
|
282
|
+
if (client.isGrpcWeb && envelope.count === undefined) {
|
|
283
|
+
return { ...envelope, count: await grpcExactCount(layer, params, descriptor) };
|
|
284
|
+
}
|
|
285
|
+
return envelope;
|
|
271
286
|
},
|
|
272
287
|
async *stream(request) {
|
|
273
288
|
ensureCapability(descriptor, caps, "stream");
|
|
@@ -4033,6 +4048,176 @@ function aggregateResultFromUntyped(response, _alias) {
|
|
|
4033
4048
|
fields: response.fields,
|
|
4034
4049
|
};
|
|
4035
4050
|
}
|
|
4051
|
+
async function grpcExactCount(layer, params, descriptor) {
|
|
4052
|
+
const { resultOffset: _offset, resultRecordCount: _limit, outFields: _fields, ...rest } = params;
|
|
4053
|
+
void _offset;
|
|
4054
|
+
void _limit;
|
|
4055
|
+
void _fields;
|
|
4056
|
+
const { returnExtentOnly: _extent, ...extraParams } = rest.extraParams ?? {};
|
|
4057
|
+
void _extent;
|
|
4058
|
+
const response = await layer.client.queryFeatures({
|
|
4059
|
+
serviceId: layer.serviceId,
|
|
4060
|
+
layerId: layer.layerId,
|
|
4061
|
+
...rest,
|
|
4062
|
+
returnGeometry: false,
|
|
4063
|
+
extraParams: { ...extraParams, returnCountOnly: true },
|
|
4064
|
+
});
|
|
4065
|
+
const count = typeof response === "object" && response !== null ? response.count : undefined;
|
|
4066
|
+
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) {
|
|
4067
|
+
throw new HonuaCapabilityNotSupportedError("count", "grpc", descriptor.id, {
|
|
4068
|
+
context: { reason: "gRPC count response did not contain an exact nonnegative count." },
|
|
4069
|
+
});
|
|
4070
|
+
}
|
|
4071
|
+
return count;
|
|
4072
|
+
}
|
|
4073
|
+
function grpcAggregateError(sourceId, context) {
|
|
4074
|
+
return new HonuaCapabilityNotSupportedError("queryAggregate", "grpc", sourceId, { context });
|
|
4075
|
+
}
|
|
4076
|
+
async function grpcMaterializedAggregate(layer, request, descriptor) {
|
|
4077
|
+
if (request.aggregation.histogram || request.aggregation.timeSeries) {
|
|
4078
|
+
throw grpcAggregateError(descriptor.id);
|
|
4079
|
+
}
|
|
4080
|
+
if (request.aggregation.groupBy?.includes("*") ||
|
|
4081
|
+
request.aggregation.metrics.some((metric) => metric.field === "*" && metric.fn !== "count")) {
|
|
4082
|
+
throw grpcAggregateError(descriptor.id, { reason: "Wildcard fields are supported only for COUNT(*)." });
|
|
4083
|
+
}
|
|
4084
|
+
// Aggregate the whole matching set, independently of the caller's display
|
|
4085
|
+
// window. Never return a partial aggregate when the finite drain budget ends.
|
|
4086
|
+
const params = toFeatureLayerRequest({ ...request, pagination: undefined }, descriptor);
|
|
4087
|
+
const expectedCount = await grpcExactCount(layer, params, descriptor);
|
|
4088
|
+
const pageSize = 2000;
|
|
4089
|
+
const maxPages = 100;
|
|
4090
|
+
if (expectedCount > pageSize * maxPages) {
|
|
4091
|
+
throw grpcAggregateError(descriptor.id, {
|
|
4092
|
+
reason: "Matching set exceeds the 200000-feature client-side aggregation budget.",
|
|
4093
|
+
expectedCount,
|
|
4094
|
+
});
|
|
4095
|
+
}
|
|
4096
|
+
const features = [];
|
|
4097
|
+
const ids = new Set();
|
|
4098
|
+
for (let page = 0; page < maxPages && (page === 0 || features.length < expectedCount); page += 1) {
|
|
4099
|
+
const response = await layer.queryFeatures({
|
|
4100
|
+
...params,
|
|
4101
|
+
outFields: ["*"],
|
|
4102
|
+
returnGeometry: false,
|
|
4103
|
+
resultOffset: features.length,
|
|
4104
|
+
resultRecordCount: pageSize,
|
|
4105
|
+
});
|
|
4106
|
+
const requiredFields = [
|
|
4107
|
+
...(request.aggregation.groupBy ?? []),
|
|
4108
|
+
...request.aggregation.metrics.map((metric) => metric.field),
|
|
4109
|
+
].filter((field) => field !== "*");
|
|
4110
|
+
for (const field of requiredFields) {
|
|
4111
|
+
if (response.fields?.filter((definition) => definition.name.toLowerCase() === field.toLowerCase()).length !== 1) {
|
|
4112
|
+
throw grpcAggregateError(descriptor.id, {
|
|
4113
|
+
reason: "Aggregate field is unknown or ambiguous in the server field schema.",
|
|
4114
|
+
field,
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
const rows = response.features ?? [];
|
|
4119
|
+
if (rows.length > pageSize) {
|
|
4120
|
+
throw grpcAggregateError(descriptor.id, {
|
|
4121
|
+
reason: "gRPC response exceeded the requested materialization page budget.",
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
if (rows.length === 0)
|
|
4125
|
+
break;
|
|
4126
|
+
for (const feature of rows) {
|
|
4127
|
+
const id = response.objectIdFieldName
|
|
4128
|
+
? grpcAggregateField(feature.attributes, response.objectIdFieldName)
|
|
4129
|
+
: undefined;
|
|
4130
|
+
if ((typeof id !== "number" && typeof id !== "string") ||
|
|
4131
|
+
(typeof id === "number" && !Number.isSafeInteger(id)) ||
|
|
4132
|
+
id === "" ||
|
|
4133
|
+
ids.has(String(id))) {
|
|
4134
|
+
throw grpcAggregateError(descriptor.id, {
|
|
4135
|
+
reason: "gRPC materialization returned a missing or repeated object ID; no aggregate was returned.",
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
ids.add(String(id));
|
|
4139
|
+
}
|
|
4140
|
+
features.push(...rows);
|
|
4141
|
+
if (features.length > expectedCount || (!response.exceededTransferLimit && rows.length < pageSize))
|
|
4142
|
+
break;
|
|
4143
|
+
}
|
|
4144
|
+
if (features.length !== expectedCount) {
|
|
4145
|
+
throw grpcAggregateError(descriptor.id, {
|
|
4146
|
+
reason: "gRPC materialization did not match the exact count; no partial aggregate was returned.",
|
|
4147
|
+
expectedCount,
|
|
4148
|
+
actualCount: features.length,
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
return {
|
|
4152
|
+
features,
|
|
4153
|
+
exceededTransferLimit: false,
|
|
4154
|
+
totalCount: expectedCount,
|
|
4155
|
+
aggregateRows: grpcAggregateRows(features, request.aggregation),
|
|
4156
|
+
degraded: [
|
|
4157
|
+
{
|
|
4158
|
+
capability: "queryAggregate",
|
|
4159
|
+
reason: "gRPC does not expose server-side aggregation; evaluated client-side over the complete matching set.",
|
|
4160
|
+
protocol: "grpc",
|
|
4161
|
+
sourceId: descriptor.id,
|
|
4162
|
+
},
|
|
4163
|
+
],
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
// SQL field aggregates count non-null field values, use null for an empty
|
|
4167
|
+
// numeric input, and use sample variance like the canonical Postgres provider.
|
|
4168
|
+
// Keep this transport fallback separate from existing OGC page evaluation.
|
|
4169
|
+
function grpcAggregateField(attributes, field) {
|
|
4170
|
+
if (typeof attributes !== "object" || attributes === null)
|
|
4171
|
+
return undefined;
|
|
4172
|
+
const key = Object.keys(attributes).find((key) => key.toLowerCase() === field.toLowerCase());
|
|
4173
|
+
return key === undefined ? undefined : attributes[key];
|
|
4174
|
+
}
|
|
4175
|
+
function grpcAggregateRows(features, aggregation) {
|
|
4176
|
+
const groups = new Map();
|
|
4177
|
+
const groupFields = aggregation.groupBy ?? [];
|
|
4178
|
+
if (groupFields.length === 0)
|
|
4179
|
+
groups.set("[]", { keys: [], rows: [] });
|
|
4180
|
+
for (const feature of features) {
|
|
4181
|
+
const keys = groupFields.map((field) => grpcAggregateField(feature.attributes, field) ?? null);
|
|
4182
|
+
const key = JSON.stringify(keys);
|
|
4183
|
+
let group = groups.get(key);
|
|
4184
|
+
if (!group) {
|
|
4185
|
+
group = { keys, rows: [] };
|
|
4186
|
+
groups.set(key, group);
|
|
4187
|
+
}
|
|
4188
|
+
group.rows.push(feature);
|
|
4189
|
+
}
|
|
4190
|
+
return Array.from(groups.values(), ({ keys, rows }) => {
|
|
4191
|
+
const output = Object.fromEntries(groupFields.map((field, i) => [field, keys[i]]));
|
|
4192
|
+
for (const metric of aggregation.metrics) {
|
|
4193
|
+
const values = rows
|
|
4194
|
+
.map((row) => grpcAggregateField(row.attributes, metric.field))
|
|
4195
|
+
.filter((value) => value != null);
|
|
4196
|
+
const alias = metric.alias ?? `${metric.fn}_${metric.field}`;
|
|
4197
|
+
if (metric.fn === "count") {
|
|
4198
|
+
output[alias] = metric.field === "*" ? rows.length : values.length;
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
if (values.some((value) => !Number.isFinite(value))) {
|
|
4202
|
+
throw grpcAggregateError(undefined, {
|
|
4203
|
+
reason: "Numeric client-side aggregation requires finite numeric field values.",
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
4206
|
+
const numbers = values;
|
|
4207
|
+
if (numbers.length === 0 || ((metric.fn === "stddev" || metric.fn === "var") && numbers.length < 2)) {
|
|
4208
|
+
output[alias] = null;
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4211
|
+
output[alias] = applyAggregation(metric.fn, numbers, rows.length, true);
|
|
4212
|
+
if (typeof output[alias] === "number" && !Number.isFinite(output[alias])) {
|
|
4213
|
+
throw grpcAggregateError(undefined, {
|
|
4214
|
+
reason: "Client-side aggregation overflowed; no nonfinite aggregate was returned.",
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
return output;
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4036
4221
|
function clientSideAggregate(features, aggregation) {
|
|
4037
4222
|
const groupKeys = aggregation.groupBy ?? [];
|
|
4038
4223
|
if (groupKeys.length === 0) {
|
|
@@ -4071,7 +4256,7 @@ function computeMetrics(features, metrics) {
|
|
|
4071
4256
|
}
|
|
4072
4257
|
return out;
|
|
4073
4258
|
}
|
|
4074
|
-
function applyAggregation(fn, values, totalLength) {
|
|
4259
|
+
function applyAggregation(fn, values, totalLength, sampleVariance = false) {
|
|
4075
4260
|
if (fn === "count")
|
|
4076
4261
|
return totalLength;
|
|
4077
4262
|
if (values.length === 0)
|
|
@@ -4082,13 +4267,13 @@ function applyAggregation(fn, values, totalLength) {
|
|
|
4082
4267
|
case "avg":
|
|
4083
4268
|
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
4084
4269
|
case "min":
|
|
4085
|
-
return Math.min(
|
|
4270
|
+
return values.reduce((a, b) => Math.min(a, b));
|
|
4086
4271
|
case "max":
|
|
4087
|
-
return Math.max(
|
|
4272
|
+
return values.reduce((a, b) => Math.max(a, b));
|
|
4088
4273
|
case "stddev":
|
|
4089
4274
|
case "var": {
|
|
4090
4275
|
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
4091
|
-
const variance = values.reduce((a, b) => a + (b - mean) ** 2, 0) / values.length;
|
|
4276
|
+
const variance = values.reduce((a, b) => a + (b - mean) ** 2, 0) / (values.length - (sampleVariance ? 1 : 0));
|
|
4092
4277
|
return fn === "var" ? variance : Math.sqrt(variance);
|
|
4093
4278
|
}
|
|
4094
4279
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honua/sdk-esri-compat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "ArcGIS JS API compatibility layer for ArcGIS migration from Esri apps to the open Honua + MapLibre stack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gis",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"proj4": "^2.20.9"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@honua/sdk": "0.1.
|
|
49
|
+
"@honua/sdk": "0.1.18",
|
|
50
50
|
"@bufbuild/protobuf": "^2.0.0",
|
|
51
51
|
"@connectrpc/connect": "^2.0.0",
|
|
52
52
|
"@connectrpc/connect-web": "^2.0.0",
|