create-honua-app 0.1.1 → 0.1.4

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/README.md CHANGED
@@ -33,7 +33,7 @@ npm create honua-app@latest my-map -- --template react-ts
33
33
  | Template | What it shows |
34
34
  | --- | --- |
35
35
  | `vanilla-ts` | `connect → inspect → explain → query → mount`: the SDK owns the MapLibre map and mounts an accepted query plan. |
36
- | `react-ts` | The app owns a plain `maplibre-gl` map; `useMountedSource` from `@honua/sdk-js/react` mounts the discovered source onto it. |
36
+ | `react-ts` | The app owns a plain `maplibre-gl` map; the same kernel connection inspects, explains, queries, and mounts onto it. |
37
37
 
38
38
  Both starters ship a committed GeoServices fixture served by the Vite dev and preview servers, so the green path
39
39
  never depends on a third-party endpoint, an account, or an API key. Set `VITE_HONUA_ENDPOINT` to run the same code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-honua-app",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold a Honua JavaScript SDK map application: Vite + TypeScript + MapLibre starters that connect and mount a source without hand-assembling peers",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Honua",
@@ -17,7 +17,16 @@
17
17
  "access": "public",
18
18
  "provenance": true
19
19
  },
20
- "keywords": ["gis", "geospatial", "maplibre", "typescript", "honua", "scaffold", "starter", "create-app"],
20
+ "keywords": [
21
+ "gis",
22
+ "geospatial",
23
+ "maplibre",
24
+ "typescript",
25
+ "honua",
26
+ "scaffold",
27
+ "starter",
28
+ "create-app"
29
+ ],
21
30
  "type": "module",
22
31
  "bin": {
23
32
  "create-honua-app": "bin/create-honua-app.mjs"
@@ -25,7 +34,14 @@
25
34
  "exports": {
26
35
  "./package.json": "./package.json"
27
36
  },
28
- "files": ["bin", "lib", "templates", "templates.manifest.json", "README.md", "LICENSE"],
37
+ "files": [
38
+ "bin",
39
+ "lib",
40
+ "templates",
41
+ "templates.manifest.json",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
29
45
  "engines": {
30
46
  "node": ">=20.19.0"
31
47
  }
@@ -1,10 +1,10 @@
1
1
  # Honua map app (React + TypeScript)
2
2
 
3
3
  A Vite + React + TypeScript starter for the [Honua JavaScript SDK](https://github.com/honua-io/honua-sdk-js). The app
4
- owns a plain `maplibre-gl` map and the SDK's React bridge mounts a discovered source onto it:
4
+ owns a plain `maplibre-gl` map and runs the kernel lifecycle against it:
5
5
 
6
6
  ```text
7
- connect → source → useMountedSource
7
+ connect → inspect → explain → mount
8
8
  ```
9
9
 
10
10
  ## Run it
@@ -34,14 +34,14 @@ endpoint or a server-side proxy.
34
34
 
35
35
  | File | Why |
36
36
  | --- | --- |
37
- | `src/App.tsx` | Connection, query, and the mounted-source hook. Change the query here. |
38
- | `src/main.tsx` | React root. `StrictMode` stays on: the SDK hooks are StrictMode-safe. |
37
+ | `src/App.tsx` | Connection, accepted query plan, and kernel-owned mount lifecycle. Change the query here. |
38
+ | `src/main.tsx` | React root. `StrictMode` stays on: cancellation and disposal make remounts safe. |
39
39
  | `src/fixture-endpoint.ts` | The same-origin path the offline fixture is served on. |
40
+ | `src/maplibre-worker.ts` | MapLibre 6's ESM worker URL, resolved through Vite. Leave it alone unless you swap the renderer. |
40
41
  | `vite.config.ts` | The fixture service. Delete it once you point the app at a real endpoint. |
41
42
 
42
- `HonuaMapProvider` publishes an app-owned map to descendants when the map is created deeper in the tree; this starter
43
- passes the map to `useMountedSource` directly. Add `@vitejs/plugin-react` if you want React Fast Refresh — Vite
44
- compiles the JSX without it.
43
+ The starter passes its app-owned map directly to `connection.mount` and disposes the kernel when React tears the
44
+ effect down. Add `@vitejs/plugin-react` if you want React Fast Refresh — Vite compiles the JSX without it.
45
45
 
46
46
  ## Dependencies
47
47
 
@@ -49,10 +49,10 @@ compiles the JSX without it.
49
49
  `@connectrpc` packages are the SDK's optional transport peers: they are installed and pinned here so the bundle builds
50
50
  without any peer-dependency assembly, and they are what a Honua-server connection uses.
51
51
 
52
- This starter pins **MapLibre GL JS 5.24.0**, the major the published `@honua/sdk-js` release accepts as an optional
53
- peer. MapLibre 6 is supported by the SDK source (`^5.0.0 || ^6.0.0`) and ships in the next release; upgrading to 6.x
54
- also means configuring MapLibre's ESM worker before the first map is created — under Vite, `setWorkerUrl` with
55
- `import maplibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url"`.
52
+ This starter pins **MapLibre GL JS 6.1.0**, which the published `@honua/sdk-js` release accepts as an optional peer
53
+ (`^5.0.0 || ^6.0.0`). MapLibre 6 is ESM-only and loads its worker as a separate module, so `src/maplibre-worker.ts`
54
+ calls `setWorkerUrl` with Vite's `?worker&url` import before the first map is created; `src/main.tsx` imports it
55
+ first. Downgrading to MapLibre 5 is supported by the SDK and means deleting that module and its import.
56
56
 
57
57
  ## Checks
58
58
 
@@ -16,8 +16,8 @@
16
16
  "@bufbuild/protobuf": "2.13.0",
17
17
  "@connectrpc/connect": "2.1.2",
18
18
  "@connectrpc/connect-web": "2.1.2",
19
- "@honua/sdk-js": "0.1.2-beta.0",
20
- "maplibre-gl": "5.24.0",
19
+ "@honua/sdk-js": "0.1.10-beta.0",
20
+ "maplibre-gl": "6.1.0",
21
21
  "react": "19.2.8",
22
22
  "react-dom": "19.2.8"
23
23
  },
@@ -1,6 +1,6 @@
1
1
  import { createHonua } from "@honua/sdk-js";
2
- import type { ConnectProtocolHint, Source } from "@honua/sdk-js";
3
- import { useMountedSource } from "@honua/sdk-js/react";
2
+ import type { ConnectProtocolHint, RendererDiagnostic } from "@honua/sdk-js";
3
+ import { maplibreRenderer } from "@honua/sdk-js/runtime";
4
4
  import * as maplibregl from "maplibre-gl";
5
5
  import type { RefObject } from "react";
6
6
  import { useEffect, useRef, useState } from "react";
@@ -34,9 +34,10 @@ const PROTOCOL = (
34
34
  const DATA_LANE = configuredEndpoint && configuredEndpoint.length > 0 ? "Live endpoint" : "Committed fixture";
35
35
 
36
36
  interface ConnectionState {
37
- readonly source: Source | null;
38
37
  readonly sourceId?: string;
39
38
  readonly protocol?: string;
39
+ readonly diagnostics?: readonly RendererDiagnostic[];
40
+ readonly mounted: boolean;
40
41
  readonly error?: string;
41
42
  }
42
43
 
@@ -65,54 +66,67 @@ function useExternalMap(): { containerRef: RefObject<HTMLDivElement | null>; map
65
66
  return { containerRef, map };
66
67
  }
67
68
 
68
- /** Connect once per effect run and publish the discovered source. */
69
- function useHonuaSource(): ConnectionState {
70
- const [state, setState] = useState<ConnectionState>({ source: null });
69
+ /** Run the canonical kernel-owned lifecycle against the app-owned map. */
70
+ function useHonuaMap(map: maplibregl.Map | null): ConnectionState {
71
+ const [state, setState] = useState<ConnectionState>({ mounted: false });
71
72
 
72
73
  useEffect(() => {
74
+ if (!map) return;
73
75
  const honua = createHonua();
74
- let cancelled = false;
76
+ const cancellation = new AbortController();
75
77
  void (async () => {
76
78
  try {
77
79
  const connection = await honua.connect(
78
80
  { url: ENDPOINT, protocol: PROTOCOL },
79
- { authorizationScopeFingerprint: "anonymous-public" },
81
+ { authorizationScopeFingerprint: "anonymous-public", signal: cancellation.signal },
80
82
  );
81
- const inspection = await connection.inspect();
83
+ const inspection = await connection.inspect({ signal: cancellation.signal });
82
84
  const sourceId = inspection.defaultSourceId ?? inspection.sources[0]?.descriptor.id;
83
85
  if (!sourceId) throw new Error("The endpoint advertised no queryable source.");
84
- if (cancelled) return;
85
- setState({ source: connection.source(sourceId), sourceId, protocol: inspection.protocol });
86
+ const plan = await connection.explain(QUERY, { sourceId, signal: cancellation.signal });
87
+ const mounted = await connection.mount(map, {
88
+ renderer: maplibreRenderer(maplibregl),
89
+ query: plan,
90
+ sourceId,
91
+ signal: cancellation.signal,
92
+ });
93
+ await mounted.ready;
94
+ setState({
95
+ sourceId,
96
+ protocol: inspection.protocol,
97
+ diagnostics: mounted.diagnostics,
98
+ mounted: true,
99
+ });
86
100
  } catch (error) {
87
- if (!cancelled) setState({ source: null, error: error instanceof Error ? error.message : String(error) });
101
+ if (!cancellation.signal.aborted) {
102
+ setState({ mounted: false, error: error instanceof Error ? error.message : String(error) });
103
+ }
88
104
  }
89
105
  })();
90
106
  return () => {
91
- cancelled = true;
107
+ cancellation.abort();
92
108
  void honua.dispose();
93
109
  };
94
- }, []);
110
+ }, [map]);
95
111
 
96
112
  return state;
97
113
  }
98
114
 
99
115
  export function App() {
100
116
  const { containerRef, map } = useExternalMap();
101
- const connection = useHonuaSource();
102
- const mounted = useMountedSource(map, connection.source, { query: QUERY, fitBounds: true });
103
-
104
- const failure = connection.error ?? (mounted.error instanceof Error ? mounted.error.message : undefined);
105
- const status = failure
106
- ? `The workflow stopped: ${failure}`
107
- : mounted.handle
108
- ? "The queried source is mounted through the React bridge."
117
+ const connection = useHonuaMap(map);
118
+
119
+ const status = connection.error
120
+ ? `The workflow stopped: ${connection.error}`
121
+ : connection.mounted
122
+ ? "The accepted plan is mounted through the kernel lifecycle."
109
123
  : "Connecting, discovering, and mounting…";
110
124
 
111
125
  return (
112
126
  <main className="app">
113
127
  <section className="panel" aria-label="Workflow">
114
128
  <p className="eyebrow">Honua JavaScript SDK · React</p>
115
- <h1 className="title">connect → source → useMountedSource</h1>
129
+ <h1 className="title">connect → inspect → explain → query → mount</h1>
116
130
  <output className="status" aria-live="polite">
117
131
  {status}
118
132
  </output>
@@ -135,11 +149,7 @@ export function App() {
135
149
  </div>
136
150
  <div>
137
151
  <dt>Strategy</dt>
138
- <dd>{mounted.diagnostics?.strategy ?? "—"}</dd>
139
- </div>
140
- <div>
141
- <dt>Features</dt>
142
- <dd id="fact-features">{mounted.diagnostics?.featureCount ?? "—"}</dd>
152
+ <dd>{connection.diagnostics?.find((item) => item.strategy)?.strategy ?? "—"}</dd>
143
153
  </div>
144
154
  </dl>
145
155
  <p className="hint">
@@ -1,3 +1,4 @@
1
+ import "./maplibre-worker.js";
1
2
  import "maplibre-gl/dist/maplibre-gl.css";
2
3
 
3
4
  import { StrictMode } from "react";
@@ -9,8 +10,8 @@ import "./styles.css";
9
10
  const container = document.getElementById("root");
10
11
  if (!container) throw new Error("Missing #root element");
11
12
 
12
- // StrictMode double-invokes effects in development. The SDK's React hooks are
13
- // StrictMode-safe, so the mount/unmount/mount cycle never leaks a map source.
13
+ // StrictMode double-invokes effects in development. App owns cancellation and
14
+ // SDK disposal, so the mount/unmount/mount cycle never leaks a map source.
14
15
  createRoot(container).render(
15
16
  <StrictMode>
16
17
  <App />
@@ -0,0 +1,14 @@
1
+ // MapLibre GL JS 6 is ESM-only and loads its worker as a separate module,
2
+ // resolving `./maplibre-gl-worker.mjs` against its own `import.meta.url`. That
3
+ // URL survives a dev server but not a production bundle, where MapLibre's own
4
+ // module has been rewritten into an app chunk that sits nowhere near the
5
+ // installed package and its sibling worker file. Vite's
6
+ // `?worker&url` pipeline emits the worker as a real asset and hands back the
7
+ // hashed URL it was emitted to, which is what `setWorkerUrl` needs.
8
+ //
9
+ // Import this module before the first map is created; `src/main.tsx` does.
10
+
11
+ import { setWorkerUrl } from "maplibre-gl";
12
+ import maplibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
13
+
14
+ setWorkerUrl(maplibreWorkerUrl);
@@ -36,6 +36,7 @@ endpoint or a server-side proxy.
36
36
  | --- | --- |
37
37
  | `src/main.ts` | The whole workflow: connect, inspect, explain, query, mount. Change the query here. |
38
38
  | `src/fixture-endpoint.ts` | The same-origin path the offline fixture is served on. |
39
+ | `src/maplibre-worker.ts` | MapLibre 6's ESM worker URL, resolved through Vite. Leave it alone unless you swap the renderer. |
39
40
  | `vite.config.ts` | The fixture service. Delete it once you point the app at a real endpoint. |
40
41
  | `fixtures/` | The committed GeoServices layer description and its query response. |
41
42
 
@@ -45,10 +46,10 @@ endpoint or a server-side proxy.
45
46
  `@connectrpc` packages are the SDK's optional transport peers: they are installed and pinned here so the bundle builds
46
47
  without any peer-dependency assembly, and they are what a Honua-server connection uses.
47
48
 
48
- This starter pins **MapLibre GL JS 5.24.0**, the major the published `@honua/sdk-js` release accepts as an optional
49
- peer. MapLibre 6 is supported by the SDK source (`^5.0.0 || ^6.0.0`) and ships in the next release; upgrading to 6.x
50
- also means configuring MapLibre's ESM worker before the first map is created — under Vite, `setWorkerUrl` with
51
- `import maplibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url"`.
49
+ This starter pins **MapLibre GL JS 6.1.0**, which the published `@honua/sdk-js` release accepts as an optional peer
50
+ (`^5.0.0 || ^6.0.0`). MapLibre 6 is ESM-only and loads its worker as a separate module, so `src/maplibre-worker.ts`
51
+ calls `setWorkerUrl` with Vite's `?worker&url` import before the first map is created; `src/main.ts` imports it first.
52
+ Downgrading to MapLibre 5 is supported by the SDK and means deleting that module and its import.
52
53
 
53
54
  ## Checks
54
55
 
@@ -16,8 +16,8 @@
16
16
  "@bufbuild/protobuf": "2.13.0",
17
17
  "@connectrpc/connect": "2.1.2",
18
18
  "@connectrpc/connect-web": "2.1.2",
19
- "@honua/sdk-js": "0.1.2-beta.0",
20
- "maplibre-gl": "5.24.0"
19
+ "@honua/sdk-js": "0.1.10-beta.0",
20
+ "maplibre-gl": "6.1.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "24.13.3",
@@ -1,3 +1,4 @@
1
+ import "./maplibre-worker.js";
1
2
  import "maplibre-gl/dist/maplibre-gl.css";
2
3
 
3
4
  import { createHonua } from "@honua/sdk-js";
@@ -0,0 +1,14 @@
1
+ // MapLibre GL JS 6 is ESM-only and loads its worker as a separate module,
2
+ // resolving `./maplibre-gl-worker.mjs` against its own `import.meta.url`. That
3
+ // URL survives a dev server but not a production bundle, where MapLibre's own
4
+ // module has been rewritten into an app chunk that sits nowhere near the
5
+ // installed package and its sibling worker file. Vite's
6
+ // `?worker&url` pipeline emits the worker as a real asset and hands back the
7
+ // hashed URL it was emitted to, which is what `setWorkerUrl` needs.
8
+ //
9
+ // Import this module before the first map is created; `src/main.ts` does.
10
+
11
+ import { setWorkerUrl } from "maplibre-gl";
12
+ import maplibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
13
+
14
+ setWorkerUrl(maplibreWorkerUrl);
@@ -3,7 +3,7 @@
3
3
  "schemaVersion": 1,
4
4
  "sdk": {
5
5
  "package": "@honua/sdk-js",
6
- "version": "0.1.2-beta.0"
6
+ "version": "0.1.10-beta.0"
7
7
  },
8
8
  "repository": {
9
9
  "owner": "honua-io",
@@ -42,7 +42,7 @@
42
42
  {
43
43
  "id": "react-ts",
44
44
  "title": "React TypeScript",
45
- "summary": "Vite + React + TypeScript app that mounts the same accepted plan through @honua/sdk-js/react hooks on an app-owned MapLibre map.",
45
+ "summary": "Vite + React + TypeScript app that mounts one accepted plan through the SDK kernel on an app-owned MapLibre map.",
46
46
  "path": "packages/create-honua-app/templates/react-ts",
47
47
  "entryFile": "src/App.tsx",
48
48
  "dataLane": "fixture",