@honua/react 0.1.2-beta.0 → 0.1.7-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/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +88 -19
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +4 -0
- package/contract/widget-source.d.ts.map +1 -1
- package/core/client.d.ts +51 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +234 -54
- package/core/error-classifications.d.ts +23 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +23 -0
- package/core/error-code-registry.d.ts +24 -1
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +23 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +2 -2
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +69 -5
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +327 -10
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +32 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +158 -23
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +138 -11
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -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/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/maplibre-target.js +1 -1
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/react/honua-map.d.ts +3 -1
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +11 -1
- package/react/hooks.d.ts +1 -1
- package/react/hooks.js +1 -1
- package/react/selection.d.ts.map +1 -1
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +1 -1
- package/react/use-mounted-source.js +1 -1
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/exploration.d.ts +16 -0
- package/realtime/exploration.d.ts.map +1 -0
- package/realtime/exploration.js +32 -0
- package/realtime/honua-server.d.ts +131 -0
- package/realtime/honua-server.d.ts.map +1 -0
- package/realtime/honua-server.js +302 -0
- package/realtime/index.d.ts +39 -0
- package/realtime/index.d.ts.map +1 -0
- package/realtime/index.js +26 -0
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/projections.d.ts +30 -0
- package/realtime/projections.d.ts.map +1 -0
- package/realtime/projections.js +51 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/reducer.d.ts +14 -0
- package/realtime/reducer.d.ts.map +1 -0
- package/realtime/reducer.js +351 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts +23 -0
- package/realtime/sse.d.ts.map +1 -0
- package/realtime/sse.js +153 -0
- package/realtime/store.d.ts +18 -0
- package/realtime/store.d.ts.map +1 -0
- package/realtime/store.js +63 -0
- package/realtime/types.d.ts +190 -0
- package/realtime/types.d.ts.map +1 -0
- package/realtime/types.js +10 -0
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +28 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +73 -0
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/runtime/style-compose.d.ts.map +1 -1
- package/runtime/style-compose.js +78 -9
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed client for the Studio package lifecycle API
|
|
3
|
+
* (`/api/v{version}/studio` in `honua-server`): package-family capability
|
|
4
|
+
* discovery, draft CRUD with optimistic-generation checking, validate,
|
|
5
|
+
* preview-plan, immutable content versions, version comparisons, publication
|
|
6
|
+
* requests, reopen, and rollback requests.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors {@link HonuaControlPlaneClient}'s ergonomics
|
|
9
|
+
* (`src/control-plane/client.ts`): construct with the SDK's standard
|
|
10
|
+
* {@link HonuaClient} (transport, auth, retry, and timeout are inherited from
|
|
11
|
+
* it), reach resource groups off the client instance
|
|
12
|
+
* (`.drafts`, `.contentVersions`, `.publicationRequests`, `.rollbackRequests`,
|
|
13
|
+
* `.packageFamilies`), and use `.raw()` for any endpoint that does not yet
|
|
14
|
+
* have a dedicated method.
|
|
15
|
+
*
|
|
16
|
+
* Every method throws {@link HonuaStudioError} on a non-2xx response — see
|
|
17
|
+
* `./lifecycle-errors.js` for the RFC 7807 problem-detail taxonomy and the
|
|
18
|
+
* `generation-conflict` discriminant.
|
|
19
|
+
*
|
|
20
|
+
* ## Enumeration gap
|
|
21
|
+
*
|
|
22
|
+
* The lifecycle API doc is explicit that **there is no endpoint to enumerate
|
|
23
|
+
* drafts or content items** — only single-resource reads
|
|
24
|
+
* (`GET /package-drafts/{draftId}`, `GET /content-items/{itemId}/versions`,
|
|
25
|
+
* `GET /content-items/{itemId}/versions/{versionId}`) and the
|
|
26
|
+
* family-discovery list (`GET /package-families`, which already returns every
|
|
27
|
+
* family in one response and does need no list method of its own).
|
|
28
|
+
* `honua-server#3003` tracks adding a draft/content-item enumeration
|
|
29
|
+
* endpoint. Until it ships:
|
|
30
|
+
*
|
|
31
|
+
* - `contentVersions.list(itemId)` already covers version enumeration *for a
|
|
32
|
+
* known item* — the gap is discovering `itemId`/`draftId` values in the
|
|
33
|
+
* first place, not paging a known collection.
|
|
34
|
+
* - `.raw()` reaches any endpoint honua-server adds before this client grows
|
|
35
|
+
* a dedicated method for it, without a breaking-change window.
|
|
36
|
+
* - When the enumeration endpoint ships, it is additive: a new
|
|
37
|
+
* `drafts.list(options)` (or a new resource group) can be added without
|
|
38
|
+
* touching any existing method signature.
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
import { isHonuaError } from "../core/errors.js";
|
|
43
|
+
import { HonuaStudioError, classifyStudioProblemStatus } from "./lifecycle-errors.js";
|
|
44
|
+
/** Default base path for the Studio package lifecycle API. */
|
|
45
|
+
export const HONUA_STUDIO_LIFECYCLE_BASE_PATH = "/api/v1/studio";
|
|
46
|
+
/** Construct a {@link HonuaStudioLifecycleClient} from an existing {@link HonuaClient}. */
|
|
47
|
+
export function createHonuaStudioLifecycleClient(options) {
|
|
48
|
+
return new HonuaStudioLifecycleClient(options);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Typed client for every endpoint in the Studio package lifecycle API.
|
|
52
|
+
*
|
|
53
|
+
* @experimental Not yet covered by the SDK's semver contract.
|
|
54
|
+
*/
|
|
55
|
+
export class HonuaStudioLifecycleClient {
|
|
56
|
+
#client;
|
|
57
|
+
#basePath;
|
|
58
|
+
packageFamilies;
|
|
59
|
+
drafts;
|
|
60
|
+
contentVersions;
|
|
61
|
+
publicationRequests;
|
|
62
|
+
rollbackRequests;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.#client = options.client;
|
|
65
|
+
this.#basePath = normalizeBasePath(options.basePath ?? HONUA_STUDIO_LIFECYCLE_BASE_PATH);
|
|
66
|
+
this.packageFamilies = new HonuaStudioPackageFamiliesClient(this);
|
|
67
|
+
this.drafts = new HonuaStudioDraftsClient(this);
|
|
68
|
+
this.contentVersions = new HonuaStudioContentVersionsClient(this);
|
|
69
|
+
this.publicationRequests = new HonuaStudioPublicationRequestsClient(this);
|
|
70
|
+
this.rollbackRequests = new HonuaStudioRollbackRequestsClient(this);
|
|
71
|
+
}
|
|
72
|
+
get basePath() {
|
|
73
|
+
return this.#basePath;
|
|
74
|
+
}
|
|
75
|
+
/** Escape hatch for endpoints without a dedicated method yet (see the module doc's "Enumeration gap"). */
|
|
76
|
+
raw(request) {
|
|
77
|
+
return this.request(request.method ?? "GET", request.path, request.body, {
|
|
78
|
+
signal: request.signal,
|
|
79
|
+
headers: request.headers,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** @internal */
|
|
83
|
+
async request(method, path, body, options = {}) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await this.#client.pipelineFetch(method, this.resolvePath(path), { headers: jsonHeaders(options.headers), body: body === undefined ? null : JSON.stringify(body) }, options.signal);
|
|
86
|
+
if (response.status === 204)
|
|
87
|
+
return undefined;
|
|
88
|
+
return unwrapApiResponse(await readJson(response));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw toStudioError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** @internal */
|
|
95
|
+
resolvePath(path) {
|
|
96
|
+
if (path.startsWith("http://") || path.startsWith("https://"))
|
|
97
|
+
return path;
|
|
98
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
99
|
+
if (normalized === this.#basePath || normalized.startsWith(`${this.#basePath}/`))
|
|
100
|
+
return normalized;
|
|
101
|
+
return `${this.#basePath}${normalized}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** `GET /package-families` — discover every package family's capabilities. */
|
|
105
|
+
export class HonuaStudioPackageFamiliesClient {
|
|
106
|
+
#lifecycle;
|
|
107
|
+
constructor(lifecycle) {
|
|
108
|
+
this.#lifecycle = lifecycle;
|
|
109
|
+
}
|
|
110
|
+
/** Discover every package family, schema version, format, support level, and limitations. */
|
|
111
|
+
list(options = {}) {
|
|
112
|
+
return this.#lifecycle.request("GET", "/package-families", undefined, options);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** `/package-drafts*` — mutable draft CRUD, validation, preview, and save-as-version. */
|
|
116
|
+
export class HonuaStudioDraftsClient {
|
|
117
|
+
#lifecycle;
|
|
118
|
+
constructor(lifecycle) {
|
|
119
|
+
this.#lifecycle = lifecycle;
|
|
120
|
+
}
|
|
121
|
+
/** `POST /package-drafts` — create a mutable package draft. */
|
|
122
|
+
create(request, options = {}) {
|
|
123
|
+
return this.#lifecycle.request("POST", "/package-drafts", request, options);
|
|
124
|
+
}
|
|
125
|
+
/** `GET /package-drafts/{draftId}` — retrieve a mutable draft. */
|
|
126
|
+
get(draftId, options = {}) {
|
|
127
|
+
return this.#lifecycle.request("GET", `/package-drafts/${encodeURIComponent(draftId)}`, undefined, options);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `PUT /package-drafts/{draftId}` — replace a mutable draft with optional
|
|
131
|
+
* optimistic `generation` checking. A stale `generation` throws
|
|
132
|
+
* {@link HonuaStudioError} with `code: "generation-conflict"`.
|
|
133
|
+
*/
|
|
134
|
+
replace(draftId, request, options = {}) {
|
|
135
|
+
return this.#lifecycle.request("PUT", `/package-drafts/${encodeURIComponent(draftId)}`, request, options);
|
|
136
|
+
}
|
|
137
|
+
/** `DELETE /package-drafts/{draftId}` — delete a draft. */
|
|
138
|
+
async delete(draftId, options = {}) {
|
|
139
|
+
await this.#lifecycle.request("DELETE", `/package-drafts/${encodeURIComponent(draftId)}`, undefined, options);
|
|
140
|
+
}
|
|
141
|
+
/** `POST /package-drafts/{draftId}/validate` — re-run validation, persisting the summary on the draft. */
|
|
142
|
+
validate(draftId, options = {}) {
|
|
143
|
+
return this.#lifecycle.request("POST", `/package-drafts/${encodeURIComponent(draftId)}/validate`, undefined, options);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* `POST /package-drafts/{draftId}/preview-plan` — return a stable preview
|
|
147
|
+
* plan. `gp`/`etl`/`workflow` drafts advertise job-backed previews.
|
|
148
|
+
*/
|
|
149
|
+
previewPlan(draftId, options = {}) {
|
|
150
|
+
return this.#lifecycle.request("POST", `/package-drafts/${encodeURIComponent(draftId)}/preview-plan`, undefined, options);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* `POST /package-drafts/{draftId}/content-versions` — save a draft as an
|
|
154
|
+
* immutable content version and move the item's current pointer.
|
|
155
|
+
*/
|
|
156
|
+
createContentVersion(draftId, options = {}) {
|
|
157
|
+
return this.#lifecycle.request("POST", `/package-drafts/${encodeURIComponent(draftId)}/content-versions`, undefined, options);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** `/content-items/{itemId}/versions*` and `/version-comparisons` — immutable content versions. */
|
|
161
|
+
export class HonuaStudioContentVersionsClient {
|
|
162
|
+
#lifecycle;
|
|
163
|
+
constructor(lifecycle) {
|
|
164
|
+
this.#lifecycle = lifecycle;
|
|
165
|
+
}
|
|
166
|
+
/** `GET /content-items/{itemId}/versions` — list immutable versions ordered by `versionNumber`. */
|
|
167
|
+
list(itemId, options = {}) {
|
|
168
|
+
return this.#lifecycle.request("GET", `/content-items/${encodeURIComponent(itemId)}/versions`, undefined, options);
|
|
169
|
+
}
|
|
170
|
+
/** `GET /content-items/{itemId}/versions/{versionId}` — retrieve one immutable version. */
|
|
171
|
+
get(itemId, versionId, options = {}) {
|
|
172
|
+
return this.#lifecycle.request("GET", `/content-items/${encodeURIComponent(itemId)}/versions/${encodeURIComponent(versionId)}`, undefined, options);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* `POST /content-items/{itemId}/version-comparisons` — compare two
|
|
176
|
+
* immutable versions by content hash, dependencies, validation, and
|
|
177
|
+
* provenance.
|
|
178
|
+
*/
|
|
179
|
+
compare(itemId, request, options = {}) {
|
|
180
|
+
return this.#lifecycle.request("POST", `/content-items/${encodeURIComponent(itemId)}/version-comparisons`, request, options);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `POST /content-items/{itemId}/versions/{versionId}/reopen` — copy an
|
|
184
|
+
* immutable version into a new mutable draft with `baseVersionId` set.
|
|
185
|
+
*/
|
|
186
|
+
reopen(itemId, versionId, request = {}, options = {}) {
|
|
187
|
+
return this.#lifecycle.request("POST", `/content-items/${encodeURIComponent(itemId)}/versions/${encodeURIComponent(versionId)}/reopen`, request, options);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** `/content-items/{itemId}/versions/{versionId}/publish-requests`. */
|
|
191
|
+
export class HonuaStudioPublicationRequestsClient {
|
|
192
|
+
#lifecycle;
|
|
193
|
+
constructor(lifecycle) {
|
|
194
|
+
this.#lifecycle = lifecycle;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* `POST /content-items/{itemId}/versions/{versionId}/publish-requests` —
|
|
198
|
+
* persist a publication request and move the published pointer when
|
|
199
|
+
* validation permits.
|
|
200
|
+
*/
|
|
201
|
+
create(itemId, versionId, request = {}, options = {}) {
|
|
202
|
+
return this.#lifecycle.request("POST", `/content-items/${encodeURIComponent(itemId)}/versions/${encodeURIComponent(versionId)}/publish-requests`, request, options);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** `/content-items/{itemId}/rollback-requests`. */
|
|
206
|
+
export class HonuaStudioRollbackRequestsClient {
|
|
207
|
+
#lifecycle;
|
|
208
|
+
constructor(lifecycle) {
|
|
209
|
+
this.#lifecycle = lifecycle;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `POST /content-items/{itemId}/rollback-requests` — persist a rollback
|
|
213
|
+
* request and move the current, published, or both pointers to an earlier
|
|
214
|
+
* immutable version.
|
|
215
|
+
*/
|
|
216
|
+
create(itemId, request, options = {}) {
|
|
217
|
+
return this.#lifecycle.request("POST", `/content-items/${encodeURIComponent(itemId)}/rollback-requests`, request, options);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function normalizeBasePath(path) {
|
|
221
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
222
|
+
let end = normalized.length;
|
|
223
|
+
while (end > 1 && normalized[end - 1] === "/")
|
|
224
|
+
end -= 1;
|
|
225
|
+
return normalized.slice(0, end);
|
|
226
|
+
}
|
|
227
|
+
function jsonHeaders(headers) {
|
|
228
|
+
return { Accept: "application/json", "Content-Type": "application/json", ...headersToRecord(headers) };
|
|
229
|
+
}
|
|
230
|
+
function headersToRecord(headers) {
|
|
231
|
+
if (!headers)
|
|
232
|
+
return {};
|
|
233
|
+
if (headers instanceof Headers)
|
|
234
|
+
return Object.fromEntries(headers.entries());
|
|
235
|
+
if (Array.isArray(headers))
|
|
236
|
+
return Object.fromEntries(headers);
|
|
237
|
+
return { ...headers };
|
|
238
|
+
}
|
|
239
|
+
async function readJson(response) {
|
|
240
|
+
const text = await response.text();
|
|
241
|
+
if (!text)
|
|
242
|
+
return undefined;
|
|
243
|
+
return JSON.parse(text);
|
|
244
|
+
}
|
|
245
|
+
/** Unwrap `ApiResponse<T>` (`{ success, data, timestamp }`) into `T`, tolerating an already-unwrapped body. */
|
|
246
|
+
function unwrapApiResponse(body) {
|
|
247
|
+
if (isRecord(body) && "data" in body && "success" in body) {
|
|
248
|
+
return body.data;
|
|
249
|
+
}
|
|
250
|
+
return body;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* True when `error` has the exact shape a `HonuaHttpError` carries —
|
|
254
|
+
* deliberately **not** `error instanceof HonuaHttpError`.
|
|
255
|
+
*
|
|
256
|
+
* This module ships as part of the generated `@honua/app-platform/studio`
|
|
257
|
+
* split package (see `src/studio/index.ts`'s module doc and
|
|
258
|
+
* `scripts/prepare-split-packages.mjs`, which copies `core/` — including
|
|
259
|
+
* `core/errors.js` — into every split package rather than importing it from
|
|
260
|
+
* the `@honua/sdk` peer). A caller that wires this client to a `HonuaClient`
|
|
261
|
+
* constructed from that peer's own copy of `@honua/sdk-js` therefore has
|
|
262
|
+
* *two* separate `HonuaHttpError` classes in play — one per package copy of
|
|
263
|
+
* the identical source file. `error instanceof HonuaHttpError` silently
|
|
264
|
+
* returns `false` across that boundary even though `error` is exactly a
|
|
265
|
+
* `HonuaHttpError`, a classic dual-package hazard.
|
|
266
|
+
*
|
|
267
|
+
* Detection instead follows the SDK's own established cross-realm error
|
|
268
|
+
* guard, {@link isHonuaError} (`isHonuaSdkError`, `src/core/error-envelope.js`
|
|
269
|
+
* — "Cross-realm type guard backed by the public tag and registered code"):
|
|
270
|
+
* match on the frozen `sdkCode` tag every `HonuaHttpError` carries
|
|
271
|
+
* (`"core.http.transient"` / `"core.http.rejected"` — byte-identical string
|
|
272
|
+
* constants in every copy of the module, unlike the class's identity) plus a
|
|
273
|
+
* direct, non-`instanceof` read of the HTTP-specific `statusCode` own
|
|
274
|
+
* property. `HonuaStudioError` is built from these primitive values alone, so
|
|
275
|
+
* its construction never depends on receiving a specific class instance
|
|
276
|
+
* either.
|
|
277
|
+
*/
|
|
278
|
+
function isHonuaHttpErrorLike(error) {
|
|
279
|
+
if (!isHonuaError(error))
|
|
280
|
+
return false;
|
|
281
|
+
if (error.sdkCode !== "core.http.transient" && error.sdkCode !== "core.http.rejected")
|
|
282
|
+
return false;
|
|
283
|
+
return typeof error.statusCode === "number";
|
|
284
|
+
}
|
|
285
|
+
/** Convert a thrown `HonuaHttpError`-shaped rejection into a typed {@link HonuaStudioError}; pass through anything else. */
|
|
286
|
+
function toStudioError(error) {
|
|
287
|
+
if (!isHonuaHttpErrorLike(error))
|
|
288
|
+
return error;
|
|
289
|
+
const problem = toProblemDetails(error.body);
|
|
290
|
+
const code = classifyStudioProblemStatus(error.statusCode);
|
|
291
|
+
const message = problem?.detail ?? problem?.title ?? error.message;
|
|
292
|
+
return new HonuaStudioError(code, error.statusCode, message, problem, { cause: error });
|
|
293
|
+
}
|
|
294
|
+
function toProblemDetails(body) {
|
|
295
|
+
return isRecord(body) ? body : undefined;
|
|
296
|
+
}
|
|
297
|
+
function isRecord(value) {
|
|
298
|
+
return typeof value === "object" && value !== null;
|
|
299
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for {@link HonuaStudioLifecycleClient}.
|
|
3
|
+
*
|
|
4
|
+
* Every non-2xx response from the Studio package lifecycle API
|
|
5
|
+
* (`/api/v{version}/studio`) is either a handler-generated client error
|
|
6
|
+
* (`400`/`404`/`409`) or a caught internal failure (`500`), and both use
|
|
7
|
+
* RFC 7807 problem details with `type: "https://honua.io/problems/studio"`
|
|
8
|
+
* (see `docs/internal/admin-api/studio-package-lifecycle.md` in
|
|
9
|
+
* `honua-server`). {@link HonuaStudioError} normalizes both into one typed
|
|
10
|
+
* shape so callers branch on `.code` instead of parsing HTTP status numbers
|
|
11
|
+
* or problem-detail strings.
|
|
12
|
+
*
|
|
13
|
+
* `generation-conflict` is a distinct discriminant (not folded into a
|
|
14
|
+
* generic `"conflict"` bucket) because it is the one Studio failure mode the
|
|
15
|
+
* SDK expects callers to *recover from automatically* — reload the draft and
|
|
16
|
+
* retry — rather than surface to a user. Use
|
|
17
|
+
* {@link isHonuaStudioGenerationConflict} to branch on it without comparing
|
|
18
|
+
* `.code` strings directly.
|
|
19
|
+
*
|
|
20
|
+
* This module intentionally does not join the central `HonuaSdkError`
|
|
21
|
+
* registry (`src/core/error-code-registry.ts`): `studio` is an
|
|
22
|
+
* `@experimental`, not-yet-semver-covered domain, matching the existing
|
|
23
|
+
* `HonuaCollaborationError` (`src/collaboration/errors.ts`) precedent for
|
|
24
|
+
* experimental-domain errors pending a scoped migration.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Discriminant classifying a {@link HonuaStudioError}. Derived from the HTTP
|
|
30
|
+
* status code of the failed response:
|
|
31
|
+
*
|
|
32
|
+
* - `validation` — `400`, request or envelope failed validation.
|
|
33
|
+
* - `not-found` — `404`, the draft/version/content item does not exist (or,
|
|
34
|
+
* per the API doc, is not reachable from the given `itemId`/`versionId`
|
|
35
|
+
* pair — cross-item ids are treated as not found).
|
|
36
|
+
* - `generation-conflict` — `409`, the draft's server-managed `generation`
|
|
37
|
+
* changed since the caller last read it (`PUT /package-drafts/{draftId}`
|
|
38
|
+
* optimistic-concurrency check). Reload the draft and retry.
|
|
39
|
+
* - `internal` — `500`, a caught internal failure.
|
|
40
|
+
* - `unknown` — any other status the doc does not enumerate.
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
export type HonuaStudioProblemKind = "validation" | "not-found" | "generation-conflict" | "internal" | "unknown";
|
|
45
|
+
/**
|
|
46
|
+
* RFC 7807 problem-details body returned by the Studio package lifecycle API.
|
|
47
|
+
* `type` is `"https://honua.io/problems/studio"` on every documented error
|
|
48
|
+
* response; `code` and `errors` are optional server-supplied extensions.
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
export interface HonuaStudioProblemDetails {
|
|
53
|
+
readonly type?: string;
|
|
54
|
+
readonly title?: string;
|
|
55
|
+
readonly status?: number;
|
|
56
|
+
readonly detail?: string;
|
|
57
|
+
readonly instance?: string;
|
|
58
|
+
readonly code?: string;
|
|
59
|
+
readonly errors?: Record<string, readonly string[]>;
|
|
60
|
+
readonly [extra: string]: unknown;
|
|
61
|
+
}
|
|
62
|
+
/** Canonical RFC 7807 `type` URI the Studio lifecycle API uses for every problem response. */
|
|
63
|
+
export declare const HONUA_STUDIO_PROBLEM_TYPE: "https://honua.io/problems/studio";
|
|
64
|
+
/**
|
|
65
|
+
* Thrown by every {@link HonuaStudioLifecycleClient} method when the server
|
|
66
|
+
* returns a non-2xx response. Carries the raw HTTP `statusCode`, the
|
|
67
|
+
* classified `.code` discriminant (see {@link HonuaStudioProblemKind}), and
|
|
68
|
+
* the parsed RFC 7807 `.problem` body when the response was problem+json.
|
|
69
|
+
*
|
|
70
|
+
* @experimental
|
|
71
|
+
*/
|
|
72
|
+
export declare class HonuaStudioError extends Error {
|
|
73
|
+
readonly code: HonuaStudioProblemKind;
|
|
74
|
+
readonly statusCode: number;
|
|
75
|
+
readonly problem: HonuaStudioProblemDetails | undefined;
|
|
76
|
+
constructor(code: HonuaStudioProblemKind, statusCode: number, message: string, problem?: HonuaStudioProblemDetails, options?: {
|
|
77
|
+
readonly cause?: unknown;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Type guard for {@link HonuaStudioError}. */
|
|
81
|
+
export declare function isHonuaStudioError(error: unknown): error is HonuaStudioError;
|
|
82
|
+
/**
|
|
83
|
+
* True only for the `409` optimistic-concurrency discriminant — the draft's
|
|
84
|
+
* `generation` changed since the caller last read it. Callers should reload
|
|
85
|
+
* the draft (`drafts.get`) and retry the `PUT` with the fresh `generation`
|
|
86
|
+
* rather than surface this as a hard failure.
|
|
87
|
+
*/
|
|
88
|
+
export declare function isHonuaStudioGenerationConflict(error: unknown): error is HonuaStudioError;
|
|
89
|
+
/** @internal Classify an HTTP status code into a {@link HonuaStudioProblemKind}. */
|
|
90
|
+
export declare function classifyStudioProblemStatus(statusCode: number): HonuaStudioProblemKind;
|
|
91
|
+
//# sourceMappingURL=lifecycle-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-errors.d.ts","sourceRoot":"","sources":["../../../src/studio/lifecycle-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,WAAW,GAAG,qBAAqB,GAAG,UAAU,GAAG,SAAS,CAAC;AAEjH;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACpD,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,8FAA8F;AAC9F,eAAO,MAAM,yBAAyB,EAAG,kCAA2C,CAAC;AAErF;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,IAAI,EAAE,sBAAsB,CAAC;IAC7C,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,OAAO,EAAE,yBAAyB,GAAG,SAAS,CAAC;gBAG7D,IAAI,EAAE,sBAAsB,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,yBAAyB,EACnC,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAQ7C;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAEzF;AAED,oFAAoF;AACpF,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,sBAAsB,CAatF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for {@link HonuaStudioLifecycleClient}.
|
|
3
|
+
*
|
|
4
|
+
* Every non-2xx response from the Studio package lifecycle API
|
|
5
|
+
* (`/api/v{version}/studio`) is either a handler-generated client error
|
|
6
|
+
* (`400`/`404`/`409`) or a caught internal failure (`500`), and both use
|
|
7
|
+
* RFC 7807 problem details with `type: "https://honua.io/problems/studio"`
|
|
8
|
+
* (see `docs/internal/admin-api/studio-package-lifecycle.md` in
|
|
9
|
+
* `honua-server`). {@link HonuaStudioError} normalizes both into one typed
|
|
10
|
+
* shape so callers branch on `.code` instead of parsing HTTP status numbers
|
|
11
|
+
* or problem-detail strings.
|
|
12
|
+
*
|
|
13
|
+
* `generation-conflict` is a distinct discriminant (not folded into a
|
|
14
|
+
* generic `"conflict"` bucket) because it is the one Studio failure mode the
|
|
15
|
+
* SDK expects callers to *recover from automatically* — reload the draft and
|
|
16
|
+
* retry — rather than surface to a user. Use
|
|
17
|
+
* {@link isHonuaStudioGenerationConflict} to branch on it without comparing
|
|
18
|
+
* `.code` strings directly.
|
|
19
|
+
*
|
|
20
|
+
* This module intentionally does not join the central `HonuaSdkError`
|
|
21
|
+
* registry (`src/core/error-code-registry.ts`): `studio` is an
|
|
22
|
+
* `@experimental`, not-yet-semver-covered domain, matching the existing
|
|
23
|
+
* `HonuaCollaborationError` (`src/collaboration/errors.ts`) precedent for
|
|
24
|
+
* experimental-domain errors pending a scoped migration.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
/** Canonical RFC 7807 `type` URI the Studio lifecycle API uses for every problem response. */
|
|
29
|
+
export const HONUA_STUDIO_PROBLEM_TYPE = "https://honua.io/problems/studio";
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by every {@link HonuaStudioLifecycleClient} method when the server
|
|
32
|
+
* returns a non-2xx response. Carries the raw HTTP `statusCode`, the
|
|
33
|
+
* classified `.code` discriminant (see {@link HonuaStudioProblemKind}), and
|
|
34
|
+
* the parsed RFC 7807 `.problem` body when the response was problem+json.
|
|
35
|
+
*
|
|
36
|
+
* @experimental
|
|
37
|
+
*/
|
|
38
|
+
export class HonuaStudioError extends Error {
|
|
39
|
+
code;
|
|
40
|
+
statusCode;
|
|
41
|
+
problem;
|
|
42
|
+
constructor(code, statusCode, message, problem, options = {}) {
|
|
43
|
+
super(message, "cause" in options ? { cause: options.cause } : undefined);
|
|
44
|
+
this.name = "HonuaStudioError";
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.statusCode = statusCode;
|
|
47
|
+
this.problem = problem;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Type guard for {@link HonuaStudioError}. */
|
|
51
|
+
export function isHonuaStudioError(error) {
|
|
52
|
+
return error instanceof HonuaStudioError;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* True only for the `409` optimistic-concurrency discriminant — the draft's
|
|
56
|
+
* `generation` changed since the caller last read it. Callers should reload
|
|
57
|
+
* the draft (`drafts.get`) and retry the `PUT` with the fresh `generation`
|
|
58
|
+
* rather than surface this as a hard failure.
|
|
59
|
+
*/
|
|
60
|
+
export function isHonuaStudioGenerationConflict(error) {
|
|
61
|
+
return isHonuaStudioError(error) && error.code === "generation-conflict";
|
|
62
|
+
}
|
|
63
|
+
/** @internal Classify an HTTP status code into a {@link HonuaStudioProblemKind}. */
|
|
64
|
+
export function classifyStudioProblemStatus(statusCode) {
|
|
65
|
+
switch (statusCode) {
|
|
66
|
+
case 400:
|
|
67
|
+
return "validation";
|
|
68
|
+
case 404:
|
|
69
|
+
return "not-found";
|
|
70
|
+
case 409:
|
|
71
|
+
return "generation-conflict";
|
|
72
|
+
case 500:
|
|
73
|
+
return "internal";
|
|
74
|
+
default:
|
|
75
|
+
return "unknown";
|
|
76
|
+
}
|
|
77
|
+
}
|