@honua/app-platform 0.1.4-beta.0 → 0.1.9-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/index.d.ts.map +1 -1
- package/app/index.js +4 -0
- package/contract/types.d.ts +1 -1
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +4 -0
- package/control-plane/admin-client.d.ts +87 -0
- package/control-plane/admin-client.d.ts.map +1 -0
- package/control-plane/admin-client.js +169 -0
- package/control-plane/client.d.ts.map +1 -1
- package/control-plane/client.js +21 -3
- package/control-plane/commands/catalog.d.ts +133 -0
- package/control-plane/commands/catalog.d.ts.map +1 -0
- package/control-plane/commands/catalog.js +353 -0
- package/control-plane/commands/errors.d.ts +96 -0
- package/control-plane/commands/errors.d.ts.map +1 -0
- package/control-plane/commands/errors.js +130 -0
- package/control-plane/commands/identity.d.ts +71 -0
- package/control-plane/commands/identity.d.ts.map +1 -0
- package/control-plane/commands/identity.js +0 -0
- package/control-plane/commands/index.d.ts +39 -0
- package/control-plane/commands/index.d.ts.map +1 -0
- package/control-plane/commands/index.js +34 -0
- package/control-plane/commands/runtime.d.ts +146 -0
- package/control-plane/commands/runtime.d.ts.map +1 -0
- package/control-plane/commands/runtime.js +318 -0
- package/control-plane/commands/types.d.ts +226 -0
- package/control-plane/commands/types.d.ts.map +1 -0
- package/control-plane/commands/types.js +30 -0
- package/control-plane/commands/validate.d.ts +27 -0
- package/control-plane/commands/validate.d.ts.map +1 -0
- package/control-plane/commands/validate.js +132 -0
- package/control-plane/generated/admin-api.d.ts +30263 -0
- package/control-plane/generated/admin-api.d.ts.map +1 -0
- package/control-plane/generated/admin-api.js +5 -0
- package/control-plane/generated/admin-operations.d.ts +4053 -0
- package/control-plane/generated/admin-operations.d.ts.map +1 -0
- package/control-plane/generated/admin-operations.js +820 -0
- package/control-plane/index.d.ts +11 -0
- package/control-plane/index.d.ts.map +1 -1
- package/control-plane/index.js +8 -0
- package/control-plane/types.d.ts +14 -0
- package/control-plane/types.d.ts.map +1 -1
- package/controls/catalog.d.ts.map +1 -1
- package/controls/catalog.js +12 -1
- package/controls/qualification.d.ts.map +1 -1
- package/controls/qualification.js +61 -2
- package/core/client.d.ts +6 -0
- package/core/client.d.ts.map +1 -1
- package/core/client.js +12 -10
- package/core/credential-redaction.d.ts +163 -0
- package/core/credential-redaction.d.ts.map +1 -0
- package/core/credential-redaction.js +389 -0
- package/core/error-classifications.d.ts +32 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +32 -0
- package/core/error-code-registry.d.ts +33 -1
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +32 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.d.ts.map +1 -1
- package/core/geoparquet-sql.d.ts +1 -1
- package/core/geoparquet-sql.js +2 -2
- package/core/grpc-adapter.d.ts +2 -2
- package/core/grpc-adapter.d.ts.map +1 -1
- package/core/grpc-adapter.js +30 -6
- package/core/job-run-errors.d.ts +10 -0
- package/core/job-run-errors.d.ts.map +1 -0
- package/core/job-run-errors.js +14 -0
- package/core/job-run-lifecycle.d.ts +34 -0
- package/core/job-run-lifecycle.d.ts.map +1 -0
- package/core/job-run-lifecycle.js +193 -0
- package/core/ogc-processes.d.ts +38 -31
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +123 -173
- package/core/process-runner.js +15 -100
- package/core/stac.d.ts +2 -1
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +29 -11
- package/core/surfaces.d.ts +2 -8
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +21 -112
- package/core/types.d.ts +22 -8
- package/core/types.d.ts.map +1 -1
- package/gen/geospatial/v1/common_pb.d.ts +514 -0
- package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/common_pb.js +335 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/execution_types_pb.js +396 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/feature_service_pb.js +47 -0
- package/gen/geospatial/v1/index.d.ts +5 -0
- package/gen/geospatial/v1/index.d.ts.map +1 -0
- package/gen/geospatial/v1/index.js +9 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/spatial_types_pb.js +58 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/projection.js +10 -1
- package/interactions/declarative-contract.d.ts +149 -0
- package/interactions/declarative-contract.d.ts.map +1 -0
- package/interactions/declarative-contract.js +293 -0
- package/interactions/declarative.d.ts +198 -0
- package/interactions/declarative.d.ts.map +1 -0
- package/interactions/declarative.js +470 -0
- package/interactions/feature-state.d.ts +7 -0
- package/interactions/feature-state.d.ts.map +1 -1
- package/interactions/feature-state.js +10 -2
- package/map/maplibre-target.js +1 -1
- package/operate/client.d.ts +11 -1
- package/operate/client.d.ts.map +1 -1
- package/operate/client.js +39 -2
- package/operate/index.d.ts +1 -1
- package/operate/index.d.ts.map +1 -1
- package/operate/types.d.ts +40 -6
- package/operate/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/query-planner/grpc.d.ts +4 -4
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +11 -11
- package/query-planner/types.d.ts +4 -4
- package/query-planner/types.d.ts.map +1 -1
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/sse.js +7 -1
- package/runtime/errors.d.ts +1 -1
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +2 -0
- package/runtime/generated/map-package-schema-meta.d.ts +11 -0
- package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-meta.js +12 -0
- package/runtime/generated/map-package-schema-validator.d.ts +16 -0
- package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-validator.js +1422 -0
- package/runtime/index.d.ts +5 -1
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +6 -0
- package/runtime/map-package-export.d.ts +151 -0
- package/runtime/map-package-export.d.ts.map +1 -0
- package/runtime/map-package-export.js +636 -0
- package/runtime/map-package-validation.d.ts +21 -1
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +132 -95
- package/runtime/map-package.d.ts +80 -0
- package/runtime/map-package.d.ts.map +1 -1
- package/runtime/style-compose.d.ts.map +1 -1
- package/runtime/style-compose.js +78 -9
- package/scene-workspace/cesium-adapter.d.ts +8 -8
- package/scene-workspace/cesium-adapter.d.ts.map +1 -1
- package/scene-workspace/cesium-adapter.js +31 -18
- package/scene-workspace/primitives.d.ts.map +1 -1
- package/scene-workspace/primitives.js +9 -6
- package/scene-workspace/source-to-cesium.js +1 -1
- package/studio/chart-spec.d.ts +6 -6
- package/studio/chart-spec.d.ts.map +1 -1
- package/studio/chart-spec.js +6 -6
- package/studio/index.d.ts +17 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +15 -5
- package/studio/lifecycle-client.d.ts +262 -22
- package/studio/lifecycle-client.d.ts.map +1 -1
- package/studio/lifecycle-client.js +524 -24
- package/studio/lifecycle-errors.d.ts +8 -2
- package/studio/lifecycle-errors.d.ts.map +1 -1
- package/studio/lifecycle-errors.js +6 -1
- package/studio/lifecycle-types.d.ts +228 -8
- package/studio/lifecycle-types.d.ts.map +1 -1
- package/studio/publication-status.d.ts +55 -0
- package/studio/publication-status.d.ts.map +1 -0
- package/studio/publication-status.js +103 -0
- package/web-components/application-context.d.ts +186 -0
- package/web-components/application-context.d.ts.map +1 -0
- package/web-components/application-context.js +429 -0
- package/web-components/elements.d.ts +10 -1
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +187 -7
- package/web-components/export-redaction.d.ts +11 -102
- package/web-components/export-redaction.d.ts.map +1 -1
- package/web-components/export-redaction.js +7 -260
- package/web-components/feature-editor-offline.d.ts +32 -0
- package/web-components/feature-editor-offline.d.ts.map +1 -0
- package/web-components/feature-editor-offline.js +115 -0
- package/web-components/feature-editor-workflow.d.ts +85 -22
- package/web-components/feature-editor-workflow.d.ts.map +1 -1
- package/web-components/feature-editor-workflow.js +240 -0
- package/web-components/feature-editor.d.ts +20 -6
- package/web-components/feature-editor.d.ts.map +1 -1
- package/web-components/feature-editor.js +159 -7
- package/web-components/feature-inspection.d.ts +287 -0
- package/web-components/feature-inspection.d.ts.map +1 -0
- package/web-components/feature-inspection.js +1464 -0
- package/web-components/feature-mutation.d.ts +77 -0
- package/web-components/feature-mutation.d.ts.map +1 -0
- package/web-components/feature-mutation.js +137 -0
- package/web-components/feature-table-engine.d.ts +94 -4
- package/web-components/feature-table-engine.d.ts.map +1 -1
- package/web-components/feature-table-engine.js +365 -18
- package/web-components/feature-table-view.d.ts +35 -1
- package/web-components/feature-table-view.d.ts.map +1 -1
- package/web-components/feature-table-view.js +128 -12
- package/web-components/index.d.ts +14 -6
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +8 -2
- package/gen/honua/v1/feature_service_pb.d.ts +0 -918
- package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
- package/gen/honua/v1/feature_service_pb.js +0 -375
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
import { createEditSession, } from "../contract/edit-session.js";
|
|
37
37
|
import { createEditSketchWorkflow, } from "../contract/edit-sketch.js";
|
|
38
38
|
import { buildEditorFormModel, coerceEditorFieldValue, editorFieldsFromSchema, editorOperationAvailability, redactEditorAttachment, resolveActiveEditorSubtype, resolveEditorFields, resolveEditorOperations, } from "./feature-editor-model.js";
|
|
39
|
+
import { createHonuaFeatureMutationReceipt } from "./feature-mutation.js";
|
|
39
40
|
const EMPTY_UNDO = { canUndo: false, canRedo: false, undoDepth: 0, redoDepth: 0 };
|
|
40
41
|
const CONFLICT_CHOICES = ["keep-mine", "discard-mine", "reload"];
|
|
41
42
|
export class HonuaFeatureEditorWorkflow {
|
|
@@ -55,6 +56,9 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
55
56
|
#conflict;
|
|
56
57
|
#message = "";
|
|
57
58
|
#committedFeatureId;
|
|
59
|
+
#mutationReceipt;
|
|
60
|
+
#mutationId;
|
|
61
|
+
#offline;
|
|
58
62
|
#abort;
|
|
59
63
|
/**
|
|
60
64
|
* The model whose in-flight submit was cancelled. Identity, not a boolean:
|
|
@@ -120,6 +124,40 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
120
124
|
const errors = dedupeValidationErrors([...sessionErrors, ...derivedErrors]);
|
|
121
125
|
return { valid: errors.length === 0, errors };
|
|
122
126
|
}
|
|
127
|
+
/** Review the exact current attempt without calling an adapter or changing workflow state. */
|
|
128
|
+
preflight() {
|
|
129
|
+
const model = this.#model;
|
|
130
|
+
const operation = this.#operation;
|
|
131
|
+
const validation = this.validate();
|
|
132
|
+
const blockers = [];
|
|
133
|
+
if (!model || !operation)
|
|
134
|
+
blockers.push("no-draft");
|
|
135
|
+
if (this.#conflict)
|
|
136
|
+
blockers.push("conflict");
|
|
137
|
+
if (this.#status === "submitting")
|
|
138
|
+
blockers.push("submitting");
|
|
139
|
+
if (this.#status === "queued")
|
|
140
|
+
blockers.push("queued");
|
|
141
|
+
const errors = validation.errors.map(({ code, fieldName, message }) => ({
|
|
142
|
+
code,
|
|
143
|
+
...(fieldName ? { fieldName } : {}),
|
|
144
|
+
message,
|
|
145
|
+
}));
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
ready: Boolean(model && operation && validation.valid && blockers.length === 0),
|
|
148
|
+
transported: false,
|
|
149
|
+
...(model && this.#mutationId ? { mutationId: this.#mutationId } : {}),
|
|
150
|
+
source: Object.freeze({
|
|
151
|
+
sourceId: this.source.descriptor.id,
|
|
152
|
+
protocol: this.source.descriptor.protocol,
|
|
153
|
+
}),
|
|
154
|
+
...(operation ? { operation } : {}),
|
|
155
|
+
...(model && this.#identity ? { identity: Object.freeze({ ...this.#identity }) } : {}),
|
|
156
|
+
...(model ? { draft: this.#draftSummary(model) } : {}),
|
|
157
|
+
validation: Object.freeze({ valid: validation.valid, errors: Object.freeze(errors) }),
|
|
158
|
+
blockers: Object.freeze(blockers),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
123
161
|
/** Current UI state. Never contains an attachment payload. */
|
|
124
162
|
snapshot() {
|
|
125
163
|
const model = this.#model;
|
|
@@ -128,6 +166,7 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
128
166
|
status: this.#status,
|
|
129
167
|
...(this.#operation ? { operation: this.#operation } : {}),
|
|
130
168
|
...(this.#identity ? { identity: this.#identity } : {}),
|
|
169
|
+
...(model && this.#mutationId ? { mutationId: this.#mutationId } : {}),
|
|
131
170
|
operations: this.operations(),
|
|
132
171
|
...(model && this.#operation
|
|
133
172
|
? {
|
|
@@ -160,6 +199,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
160
199
|
busy: this.#status === "submitting",
|
|
161
200
|
message: this.#message,
|
|
162
201
|
...(this.#committedFeatureId !== undefined ? { committedFeatureId: this.#committedFeatureId } : {}),
|
|
202
|
+
...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
|
|
203
|
+
...(this.#offline ? { offline: this.#offline } : {}),
|
|
163
204
|
};
|
|
164
205
|
}
|
|
165
206
|
// ── draft lifecycle ────────────────────────────────────────────────────
|
|
@@ -198,6 +239,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
198
239
|
* recorded — never silently downgraded.
|
|
199
240
|
*/
|
|
200
241
|
begin(operation, feature) {
|
|
242
|
+
if (this.#status === "queued")
|
|
243
|
+
return this.snapshot();
|
|
201
244
|
const target = operation === "create" ? feature : (feature ?? this.#selection);
|
|
202
245
|
this.#abort?.abort();
|
|
203
246
|
// Any in-flight submit now belongs to a superseded draft, cancelled or not.
|
|
@@ -206,6 +249,9 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
206
249
|
this.#failures = [];
|
|
207
250
|
this.#attachments = [];
|
|
208
251
|
this.#committedFeatureId = undefined;
|
|
252
|
+
this.#mutationReceipt = undefined;
|
|
253
|
+
this.#mutationId = undefined;
|
|
254
|
+
this.#offline = undefined;
|
|
209
255
|
this.#identity = this.#identityFor(target);
|
|
210
256
|
// Resolve availability against the feature this draft would open on, not
|
|
211
257
|
// the standing selection — an explicit `feature` argument may carry a
|
|
@@ -228,6 +274,7 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
228
274
|
this.#operation = operation;
|
|
229
275
|
this.#baseline = target ? cloneFeature(target) : undefined;
|
|
230
276
|
this.#model = this.#createModel(operation, target);
|
|
277
|
+
this.#mutationId = this.#allocateMutationId();
|
|
231
278
|
if (operation === "create")
|
|
232
279
|
this.#seedCreateDefaults();
|
|
233
280
|
this.#status = "draft";
|
|
@@ -236,6 +283,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
236
283
|
}
|
|
237
284
|
/** Discards the draft and aborts any in-flight submit. */
|
|
238
285
|
cancel() {
|
|
286
|
+
if (this.#status === "queued")
|
|
287
|
+
return this.snapshot();
|
|
239
288
|
if (this.#status === "submitting")
|
|
240
289
|
this.#cancelledModel = this.#model;
|
|
241
290
|
this.#abort?.abort();
|
|
@@ -246,14 +295,21 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
246
295
|
this.#conflict = undefined;
|
|
247
296
|
this.#failures = [];
|
|
248
297
|
this.#attachments = [];
|
|
298
|
+
this.#mutationReceipt = undefined;
|
|
299
|
+
this.#mutationId = undefined;
|
|
300
|
+
this.#offline = undefined;
|
|
249
301
|
this.#status = "cancelled";
|
|
250
302
|
this.#message = "Edit cancelled. Nothing was sent to the service.";
|
|
251
303
|
return this.#notify();
|
|
252
304
|
}
|
|
253
305
|
/** Reverts the draft to the feature it opened on, keeping the draft open. */
|
|
254
306
|
discardChanges() {
|
|
307
|
+
if (this.#status === "queued")
|
|
308
|
+
return this.snapshot();
|
|
255
309
|
this.#model?.discard();
|
|
256
310
|
this.#attachments = [];
|
|
311
|
+
this.#mutationReceipt = undefined;
|
|
312
|
+
this.#offline = undefined;
|
|
257
313
|
this.#failures = [];
|
|
258
314
|
this.#conflict = undefined;
|
|
259
315
|
if (this.#model)
|
|
@@ -268,6 +324,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
268
324
|
* never reaches transport as a string).
|
|
269
325
|
*/
|
|
270
326
|
setValue(name, raw) {
|
|
327
|
+
if (this.#status === "queued")
|
|
328
|
+
return this.snapshot();
|
|
271
329
|
const model = this.#model;
|
|
272
330
|
if (!model)
|
|
273
331
|
return this.snapshot();
|
|
@@ -278,6 +336,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
278
336
|
}
|
|
279
337
|
/** Records several already-typed attribute values at once. */
|
|
280
338
|
setValues(values) {
|
|
339
|
+
if (this.#status === "queued")
|
|
340
|
+
return this.snapshot();
|
|
281
341
|
const model = this.#model;
|
|
282
342
|
if (!model)
|
|
283
343
|
return this.snapshot();
|
|
@@ -288,6 +348,9 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
288
348
|
// ── geometry ───────────────────────────────────────────────────────────
|
|
289
349
|
/** Arms a sketch tool. Unsupported tools report their reason rather than throwing. */
|
|
290
350
|
startSketch(tool) {
|
|
351
|
+
if (this.#status === "queued") {
|
|
352
|
+
return { tool, state: "unsupported", reason: "The draft is locked while its offline edit is queued." };
|
|
353
|
+
}
|
|
291
354
|
const model = this.#model;
|
|
292
355
|
if (!model)
|
|
293
356
|
return { tool, state: "unsupported", reason: "No edit draft is open." };
|
|
@@ -301,6 +364,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
301
364
|
}
|
|
302
365
|
/** Applies a geometry through the sketch workflow (undoable). */
|
|
303
366
|
setGeometry(tool, geometry) {
|
|
367
|
+
if (this.#status === "queued")
|
|
368
|
+
return this.snapshot();
|
|
304
369
|
const model = this.#model;
|
|
305
370
|
if (!model)
|
|
306
371
|
return this.snapshot();
|
|
@@ -309,6 +374,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
309
374
|
return this.#notify();
|
|
310
375
|
}
|
|
311
376
|
undo() {
|
|
377
|
+
if (this.#status === "queued")
|
|
378
|
+
return false;
|
|
312
379
|
const undone = this.#model?.undo() ?? false;
|
|
313
380
|
if (undone) {
|
|
314
381
|
this.#afterEdit();
|
|
@@ -317,6 +384,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
317
384
|
return undone;
|
|
318
385
|
}
|
|
319
386
|
redo() {
|
|
387
|
+
if (this.#status === "queued")
|
|
388
|
+
return false;
|
|
320
389
|
const redone = this.#model?.redo() ?? false;
|
|
321
390
|
if (redone) {
|
|
322
391
|
this.#afterEdit();
|
|
@@ -330,6 +399,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
330
399
|
* only redacted metadata enters the snapshot.
|
|
331
400
|
*/
|
|
332
401
|
stageAttachment(attachment, options = {}) {
|
|
402
|
+
if (this.#status === "queued")
|
|
403
|
+
return this.snapshot();
|
|
333
404
|
const model = this.#model;
|
|
334
405
|
if (!model)
|
|
335
406
|
return this.snapshot();
|
|
@@ -340,6 +411,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
340
411
|
}
|
|
341
412
|
/** Stages an attachment deletion. */
|
|
342
413
|
stageAttachmentDelete(attachmentIds, options = {}) {
|
|
414
|
+
if (this.#status === "queued")
|
|
415
|
+
return this.snapshot();
|
|
343
416
|
const model = this.#model;
|
|
344
417
|
if (!model)
|
|
345
418
|
return this.snapshot();
|
|
@@ -356,6 +429,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
356
429
|
* attachments; undo history restarts because tool support is a new baseline.
|
|
357
430
|
*/
|
|
358
431
|
configureSketchTools(tools) {
|
|
432
|
+
if (this.#status === "queued")
|
|
433
|
+
return this.snapshot();
|
|
359
434
|
// Idempotent: re-declaring the same tool support must not rebuild an open
|
|
360
435
|
// draft (a sketch adapter may re-announce it on every attach).
|
|
361
436
|
if (JSON.stringify(this.#sketchTools ?? null) === JSON.stringify(tools))
|
|
@@ -387,6 +462,16 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
387
462
|
if (!model || !operation) {
|
|
388
463
|
return this.#preflightRejection("create", "No edit draft is open.", "validation");
|
|
389
464
|
}
|
|
465
|
+
if (this.#status === "queued") {
|
|
466
|
+
return {
|
|
467
|
+
status: "queued",
|
|
468
|
+
operation,
|
|
469
|
+
transported: false,
|
|
470
|
+
failures: this.#failures,
|
|
471
|
+
attachments: this.#attachments,
|
|
472
|
+
snapshot: this.snapshot(),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
390
475
|
if (this.#conflict) {
|
|
391
476
|
return this.#preflightRejection(operation, "Resolve the conflicting change before submitting again.", "conflict", "conflict");
|
|
392
477
|
}
|
|
@@ -401,8 +486,12 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
401
486
|
this.#message = "The draft was not sent: fix the highlighted problems first.";
|
|
402
487
|
return this.#commit(operation, false);
|
|
403
488
|
}
|
|
489
|
+
const mutationId = this.#mutationId ?? this.#allocateMutationId();
|
|
490
|
+
this.#mutationId = mutationId;
|
|
404
491
|
this.#status = "submitting";
|
|
405
492
|
this.#failures = [];
|
|
493
|
+
this.#mutationReceipt = undefined;
|
|
494
|
+
this.#offline = undefined;
|
|
406
495
|
this.#message = `Applying the ${operation}…`;
|
|
407
496
|
this.#notify();
|
|
408
497
|
const result = await model.submit();
|
|
@@ -426,6 +515,25 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
426
515
|
}
|
|
427
516
|
this.#failures = result.failures.map(normalizeFailure);
|
|
428
517
|
this.#attachments = attachmentDraftsFromResult(model, result.attachmentResults);
|
|
518
|
+
if (result.status === "succeeded" || result.status === "partial") {
|
|
519
|
+
const committedFeatureId = result.committedFeatureId ?? this.#identity?.featureId;
|
|
520
|
+
const feature = operation === "delete" ? undefined : featureAfterCommit(model.snapshot().feature, committedFeatureId);
|
|
521
|
+
this.#mutationReceipt = createHonuaFeatureMutationReceipt({
|
|
522
|
+
mutationId,
|
|
523
|
+
acceptedAt: this.#options.now?.() ?? new Date().toISOString(),
|
|
524
|
+
sourceId: this.source.descriptor.id,
|
|
525
|
+
protocol: this.source.descriptor.protocol,
|
|
526
|
+
operation,
|
|
527
|
+
status: result.status === "succeeded" ? "accepted" : "partially-accepted",
|
|
528
|
+
...(committedFeatureId !== undefined ? { featureId: committedFeatureId } : {}),
|
|
529
|
+
...(feature ? { feature } : {}),
|
|
530
|
+
attachmentMutations: result.attachmentResults.length,
|
|
531
|
+
optimistic: result.optimistic,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
this.#mutationReceipt = undefined;
|
|
536
|
+
}
|
|
429
537
|
const conflictFailure = result.failures.find((failure) => failure.kind === "conflict");
|
|
430
538
|
if (result.status === "succeeded") {
|
|
431
539
|
this.#status = "committed";
|
|
@@ -459,10 +567,89 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
459
567
|
if (this.#status === "submitting") {
|
|
460
568
|
return this.#preflightRejection(this.#operation ?? "create", "An edit is already in flight.", "validation");
|
|
461
569
|
}
|
|
570
|
+
if (this.#status === "queued")
|
|
571
|
+
return this.submit();
|
|
462
572
|
if (this.#status !== "conflict")
|
|
463
573
|
this.#status = "draft";
|
|
464
574
|
return this.submit();
|
|
465
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Project a durable offline queue transition into the online workflow's
|
|
578
|
+
* recoverable states. The queue remains authoritative; only identities and
|
|
579
|
+
* reason codes enter the component, never credentials or persisted payloads.
|
|
580
|
+
*/
|
|
581
|
+
applyOfflineState(state) {
|
|
582
|
+
if (state.sourceId !== this.source.descriptor.id || !this.#model || !this.#operation)
|
|
583
|
+
return this.snapshot();
|
|
584
|
+
this.#mutationId = state.idempotencyKey;
|
|
585
|
+
this.#offline = Object.freeze({ ...state });
|
|
586
|
+
this.#failures = [];
|
|
587
|
+
if (state.state === "pending" || state.state === "retryable") {
|
|
588
|
+
this.#conflict = undefined;
|
|
589
|
+
this.#status = "queued";
|
|
590
|
+
this.#message =
|
|
591
|
+
state.state === "retryable"
|
|
592
|
+
? `The edit remains offline and will retry${state.retryAt ? ` after ${state.retryAt}` : ""}.`
|
|
593
|
+
: "The edit is saved in the offline queue and will apply after reconnect.";
|
|
594
|
+
return this.#notify();
|
|
595
|
+
}
|
|
596
|
+
if (state.state === "conflicted") {
|
|
597
|
+
this.#status = "conflict";
|
|
598
|
+
this.#conflict = {
|
|
599
|
+
reason: state.conflictId
|
|
600
|
+
? `Offline replay conflict ${state.conflictId} requires a decision.`
|
|
601
|
+
: "The offline edit conflicts with the current server feature.",
|
|
602
|
+
...(this.#identity?.version !== undefined ? { mine: this.#identity.version } : {}),
|
|
603
|
+
...(state.serverGeneration !== undefined ? { theirs: state.serverGeneration } : {}),
|
|
604
|
+
choices: CONFLICT_CHOICES,
|
|
605
|
+
};
|
|
606
|
+
this.#message = "The offline edit conflicted after reconnect. Choose how to resolve it.";
|
|
607
|
+
return this.#notify();
|
|
608
|
+
}
|
|
609
|
+
if (state.state === "cancelled") {
|
|
610
|
+
this.#conflict = undefined;
|
|
611
|
+
this.#status = "rejected";
|
|
612
|
+
this.#failures = [
|
|
613
|
+
{
|
|
614
|
+
kind: "transport",
|
|
615
|
+
operation: "session",
|
|
616
|
+
description: state.reasonCode
|
|
617
|
+
? `The offline edit was cancelled: ${state.reasonCode}.`
|
|
618
|
+
: "The offline edit was cancelled before it reached the service.",
|
|
619
|
+
},
|
|
620
|
+
];
|
|
621
|
+
this.#message = this.#failures[0]?.description ?? "The offline edit was cancelled.";
|
|
622
|
+
return this.#notify();
|
|
623
|
+
}
|
|
624
|
+
if (state.state === "discarded") {
|
|
625
|
+
this.#conflict = undefined;
|
|
626
|
+
this.#model = undefined;
|
|
627
|
+
this.#operation = undefined;
|
|
628
|
+
this.#baseline = undefined;
|
|
629
|
+
this.#status = "idle";
|
|
630
|
+
this.#message = "The offline edit was discarded; the server version remains authoritative.";
|
|
631
|
+
return this.#notify();
|
|
632
|
+
}
|
|
633
|
+
const operation = this.#operation;
|
|
634
|
+
this.#conflict = undefined;
|
|
635
|
+
const featureId = this.#identity?.featureId;
|
|
636
|
+
const feature = operation === "delete" ? undefined : featureAfterCommit(this.#model.snapshot().feature, featureId);
|
|
637
|
+
this.#mutationReceipt = createHonuaFeatureMutationReceipt({
|
|
638
|
+
mutationId: state.idempotencyKey,
|
|
639
|
+
acceptedAt: state.appliedAt ?? this.#options.now?.() ?? new Date().toISOString(),
|
|
640
|
+
sourceId: this.source.descriptor.id,
|
|
641
|
+
protocol: this.source.descriptor.protocol,
|
|
642
|
+
operation,
|
|
643
|
+
status: "accepted",
|
|
644
|
+
...(featureId !== undefined ? { featureId } : {}),
|
|
645
|
+
...(feature ? { feature } : {}),
|
|
646
|
+
});
|
|
647
|
+
this.#status = "committed";
|
|
648
|
+
this.#committedFeatureId = featureId;
|
|
649
|
+
this.#baseline = cloneFeature(this.#model.snapshot().feature);
|
|
650
|
+
this.#message = "The offline edit was accepted after reconnect.";
|
|
651
|
+
return this.#notify();
|
|
652
|
+
}
|
|
466
653
|
// ── conflict resolution ────────────────────────────────────────────────
|
|
467
654
|
/**
|
|
468
655
|
* Records an explicit decision for a parked conflict.
|
|
@@ -478,6 +665,10 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
478
665
|
const conflict = this.#conflict;
|
|
479
666
|
if (!conflict)
|
|
480
667
|
return this.snapshot();
|
|
668
|
+
if (this.#offline?.state === "conflicted") {
|
|
669
|
+
this.#message = "Resolve this conflict through the offline queue so the durable edit and the UI stay consistent.";
|
|
670
|
+
return this.#notify();
|
|
671
|
+
}
|
|
481
672
|
if (choice === "reload") {
|
|
482
673
|
this.#conflict = undefined;
|
|
483
674
|
this.#model = undefined;
|
|
@@ -559,6 +750,8 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
559
750
|
* untouched and parked until {@link resolveConflict} records a decision.
|
|
560
751
|
*/
|
|
561
752
|
applyExternalChange(feature) {
|
|
753
|
+
if (this.#status === "queued")
|
|
754
|
+
return "ignored";
|
|
562
755
|
const model = this.#model;
|
|
563
756
|
const identity = this.#identity;
|
|
564
757
|
if (!model || identity === undefined || identity.featureId === undefined)
|
|
@@ -681,10 +874,43 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
681
874
|
...(token !== undefined ? { version: token } : {}),
|
|
682
875
|
};
|
|
683
876
|
}
|
|
877
|
+
#allocateMutationId() {
|
|
878
|
+
const value = this.#options.mutationId?.() ?? defaultMutationId(this.source.descriptor.id);
|
|
879
|
+
const normalized = value.trim();
|
|
880
|
+
if (!normalized)
|
|
881
|
+
throw new TypeError("mutationId must be a non-empty string.");
|
|
882
|
+
return normalized;
|
|
883
|
+
}
|
|
884
|
+
#draftSummary(model) {
|
|
885
|
+
const feature = model.snapshot().feature;
|
|
886
|
+
const current = feature.attributes;
|
|
887
|
+
const baseline = (this.#baseline?.attributes ?? {});
|
|
888
|
+
const changedFields = [...new Set([...Object.keys(baseline), ...Object.keys(current)])]
|
|
889
|
+
.filter((field) => !sameScalar(baseline[field], current[field]))
|
|
890
|
+
.sort();
|
|
891
|
+
const geometry = sameScalar(this.#baseline?.geometry, feature.geometry)
|
|
892
|
+
? "unchanged"
|
|
893
|
+
: feature.geometry === null || feature.geometry === undefined
|
|
894
|
+
? "cleared"
|
|
895
|
+
: "set";
|
|
896
|
+
const attachments = { add: 0, update: 0, delete: 0 };
|
|
897
|
+
for (const attachment of this.#attachments)
|
|
898
|
+
attachments[attachment.operation] += 1;
|
|
899
|
+
return Object.freeze({
|
|
900
|
+
dirty: model.dirty(),
|
|
901
|
+
changedFields: Object.freeze(changedFields),
|
|
902
|
+
geometry,
|
|
903
|
+
attachments: Object.freeze(attachments),
|
|
904
|
+
});
|
|
905
|
+
}
|
|
684
906
|
#afterEdit() {
|
|
685
907
|
if (this.#status === "committed" || this.#status === "rejected" || this.#status === "cancelled") {
|
|
908
|
+
if (this.#status === "committed")
|
|
909
|
+
this.#mutationId = this.#allocateMutationId();
|
|
686
910
|
this.#status = "draft";
|
|
687
911
|
this.#failures = [];
|
|
912
|
+
this.#mutationReceipt = undefined;
|
|
913
|
+
this.#offline = undefined;
|
|
688
914
|
this.#message = draftMessage(this.#operation ?? "update");
|
|
689
915
|
}
|
|
690
916
|
}
|
|
@@ -708,6 +934,7 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
708
934
|
failures: this.#failures,
|
|
709
935
|
attachments: this.#attachments,
|
|
710
936
|
snapshot: this.snapshot(),
|
|
937
|
+
...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
|
|
711
938
|
};
|
|
712
939
|
}
|
|
713
940
|
/**
|
|
@@ -738,6 +965,7 @@ export class HonuaFeatureEditorWorkflow {
|
|
|
738
965
|
failures: this.#failures,
|
|
739
966
|
attachments: this.#attachments,
|
|
740
967
|
snapshot,
|
|
968
|
+
...(this.#mutationReceipt ? { mutationReceipt: this.#mutationReceipt } : {}),
|
|
741
969
|
};
|
|
742
970
|
}
|
|
743
971
|
#notify() {
|
|
@@ -887,3 +1115,15 @@ function sameScalar(left, right) {
|
|
|
887
1115
|
}
|
|
888
1116
|
return String(left) === String(right);
|
|
889
1117
|
}
|
|
1118
|
+
let mutationSequence = 0;
|
|
1119
|
+
function defaultMutationId(sourceId) {
|
|
1120
|
+
const randomUuid = globalThis.crypto?.randomUUID;
|
|
1121
|
+
if (randomUuid)
|
|
1122
|
+
return randomUuid.call(globalThis.crypto);
|
|
1123
|
+
mutationSequence += 1;
|
|
1124
|
+
return `${String(sourceId)}:${Date.now()}:${mutationSequence}`;
|
|
1125
|
+
}
|
|
1126
|
+
function featureAfterCommit(feature, featureId) {
|
|
1127
|
+
const cloned = cloneFeature(feature);
|
|
1128
|
+
return featureId === undefined ? cloned : { ...cloned, id: featureId };
|
|
1129
|
+
}
|
|
@@ -41,16 +41,22 @@
|
|
|
41
41
|
* @module
|
|
42
42
|
*/
|
|
43
43
|
import type { CanonicalFeature } from "../contract/types.js";
|
|
44
|
-
import type {
|
|
44
|
+
import type { HonuaApplicationContext, HonuaApplicationContextChangeEvent } from "./application-context.js";
|
|
45
|
+
import type { HonuaFeatureEditorCommit, HonuaFeatureEditorConflictChoice, HonuaFeatureEditorOfflineState, HonuaFeatureEditorPreflight, HonuaFeatureEditorSnapshot, HonuaFeatureEditorWorkflow } from "./feature-editor-workflow.js";
|
|
45
46
|
import type { HonuaFeatureEditorMessages } from "./types.js";
|
|
46
47
|
declare const HTMLElementBase: typeof HTMLElement;
|
|
47
48
|
/** Detail of the `honua-feature-edit-change` event (redacted snapshot). */
|
|
48
|
-
export interface HonuaFeatureEditChangeDetail {
|
|
49
|
-
readonly snapshot: HonuaFeatureEditorSnapshot
|
|
49
|
+
export interface HonuaFeatureEditChangeDetail<T = Record<string, unknown>> {
|
|
50
|
+
readonly snapshot: HonuaFeatureEditorSnapshot<T>;
|
|
50
51
|
}
|
|
51
52
|
/** Detail of the `honua-feature-edit-commit` event (submit outcome). */
|
|
52
|
-
export interface HonuaFeatureEditCommitDetail {
|
|
53
|
-
readonly commit: HonuaFeatureEditorCommit
|
|
53
|
+
export interface HonuaFeatureEditCommitDetail<T = Record<string, unknown>> {
|
|
54
|
+
readonly commit: HonuaFeatureEditorCommit<T>;
|
|
55
|
+
}
|
|
56
|
+
/** Durable offline conflict choice requested by the editor presentation. */
|
|
57
|
+
export interface HonuaFeatureEditOfflineConflictDetail {
|
|
58
|
+
readonly choice: HonuaFeatureEditorConflictChoice;
|
|
59
|
+
readonly offline: HonuaFeatureEditorOfflineState;
|
|
54
60
|
}
|
|
55
61
|
export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> extends HTMLElementBase {
|
|
56
62
|
#private;
|
|
@@ -60,6 +66,12 @@ export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> exte
|
|
|
60
66
|
/** The workflow this editor renders. Setting it re-subscribes and re-renders. */
|
|
61
67
|
get workflow(): HonuaFeatureEditorWorkflow<T> | undefined;
|
|
62
68
|
set workflow(workflow: HonuaFeatureEditorWorkflow<T> | undefined);
|
|
69
|
+
/** Application shell context assigned and driven by `mountHonuaApplication()`. */
|
|
70
|
+
get applicationContext(): HonuaApplicationContext<T> | undefined;
|
|
71
|
+
set applicationContext(context: HonuaApplicationContext<T> | undefined);
|
|
72
|
+
honuaApplicationContextConnected(context: HonuaApplicationContext<T>): void;
|
|
73
|
+
honuaApplicationContextChanged(event: HonuaApplicationContextChangeEvent<T>): void;
|
|
74
|
+
honuaApplicationContextDisconnected(context: HonuaApplicationContext<T>): void;
|
|
63
75
|
/** Equivalent to setting `.workflow`. */
|
|
64
76
|
connect(workflow: HonuaFeatureEditorWorkflow<T>): void;
|
|
65
77
|
/**
|
|
@@ -71,7 +83,9 @@ export declare class HonuaFeatureEditorElement<T = Record<string, unknown>> exte
|
|
|
71
83
|
get selectedFeature(): CanonicalFeature<T> | undefined;
|
|
72
84
|
set selectedFeature(feature: CanonicalFeature<T> | undefined);
|
|
73
85
|
/** The last snapshot rendered. */
|
|
74
|
-
get snapshot(): HonuaFeatureEditorSnapshot | undefined;
|
|
86
|
+
get snapshot(): HonuaFeatureEditorSnapshot<T> | undefined;
|
|
87
|
+
/** Credential-free, transport-free review of the current draft. */
|
|
88
|
+
preflight(): HonuaFeatureEditorPreflight | undefined;
|
|
75
89
|
attributeChangedCallback(): void;
|
|
76
90
|
connectedCallback(): void;
|
|
77
91
|
disconnectedCallback(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-editor.d.ts","sourceRoot":"","sources":["../../../src/web-components/feature-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"feature-editor.d.ts","sourceRoot":"","sources":["../../../src/web-components/feature-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,uBAAuB,EACvB,kCAAkC,EAEnC,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EACV,wBAAwB,EACxB,gCAAgC,EAChC,8BAA8B,EAC9B,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAQ7D,QAAA,MAAM,eAAe,EAAE,OAAO,WAAkF,CAAC;AAEjH,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;CAClD;AAED,wEAAwE;AACxE,MAAM,WAAW,4BAA4B,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvE,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;CAC9C;AAED,4EAA4E;AAC5E,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,MAAM,EAAE,gCAAgC,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,8BAA8B,CAAC;CAClD;AAQD,qBAAa,yBAAyB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;;IACzF,WAAkB,kBAAkB,IAAI,MAAM,EAAE,CAE/C;IAkBD,IAAW,QAAQ,IAAI,0BAA0B,CAEhD;IACD,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,GAAG,SAAS,EAGnE;IAED,iFAAiF;IACjF,IAAW,QAAQ,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,CAE/D;IAED,IAAW,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,EAYtE;IAgBD,kFAAkF;IAClF,IAAW,kBAAkB,IAAI,uBAAuB,CAAC,CAAC,CAAC,GAAG,SAAS,CAEtE;IAED,IAAW,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,SAAS,EAI5E;IAEM,gCAAgC,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI;IAI3E,8BAA8B,CAAC,KAAK,EAAE,kCAAkC,CAAC,CAAC,CAAC,GAAG,IAAI;IAKlF,mCAAmC,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI;IAIrF,yCAAyC;IAClC,OAAO,CAAC,QAAQ,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,IAAI;IAI7D;;;;;OAKG;IACH,IAAW,eAAe,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,CAE5D;IAED,IAAW,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS,EAOlE;IAED,kCAAkC;IAClC,IAAW,QAAQ,IAAI,0BAA0B,CAAC,CAAC,CAAC,GAAG,SAAS,CAE/D;IAED,mEAAmE;IAC5D,SAAS,IAAI,2BAA2B,GAAG,SAAS;IAIpD,wBAAwB,IAAI,IAAI;IAIhC,iBAAiB,IAAI,IAAI;IAQzB,oBAAoB,IAAI,IAAI;IAgDnC,SAAS,CAAC,MAAM,IAAI,IAAI;CA0fzB;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,QAAQ,wBAA2B,GAAG,IAAI,CAKlF"}
|