@goable-io/sdk 0.2.0 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabio Carucci
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,17 +1,22 @@
1
1
  # @goable-io/sdk
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@goable-io/sdk.svg)](https://www.npmjs.com/package/@goable-io/sdk)
4
+ [![CI](https://github.com/goable-io/sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/goable-io/sdk/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
6
+
3
7
  TypeScript client for the [Goable](https://goable.io) API — 0-100 suitability
4
8
  scoring for outdoor activities (water, snow, air, land) from real-time weather
5
9
  and multi-domain physics.
6
10
 
7
- > **Status: v0.1, publish-ready (not yet on npm).** Thin, typed transport over
8
- > the public tenant-facing REST surface. Zero runtime dependencies. Works in
9
- > Node ≥18 and modern browser/edge runtimes (uses the global `fetch`).
11
+ Thin, typed transport over the public tenant-facing REST surface. **Zero runtime
12
+ dependencies.** Works in Node ≥18 and modern browser/edge runtimes (uses the
13
+ global `fetch`). Published to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements).
10
14
 
11
15
  ## Install
12
16
 
13
17
  ```bash
14
- npm install @goable-io/sdk # once published
18
+ npm install @goable-io/sdk
19
+ # or: pnpm add @goable-io/sdk
15
20
  ```
16
21
 
17
22
  ## Quickstart
@@ -21,6 +26,7 @@ import { GoableClient } from "@goable-io/sdk"
21
26
 
22
27
  const goable = new GoableClient({ apiKey: process.env.GOABLE_API_KEY! })
23
28
 
29
+ // Score a single activity at a location + time window
24
30
  const result = await goable.score({
25
31
  activity: "kitesurfing",
26
32
  location: { lat: 43.7, lng: 7.27 },
@@ -32,14 +38,44 @@ result.verdict // "unsafe" | "poor" | "marginal" | "fair" | "favorable" | "exce
32
38
  result.confidence
33
39
  ```
34
40
 
41
+ Inverse query — "where should I go?" — ranks sub-spots for an activity within
42
+ a region:
43
+
44
+ ```ts
45
+ const spots = await goable.recommendSpot({
46
+ activity: "kitesurfing",
47
+ region: { center: { lat: 43.7, lng: 7.27 }, radiusKm: 50 },
48
+ window: { from: "2026-06-01T06:00:00Z", to: "2026-06-01T18:00:00Z" },
49
+ topK: 5,
50
+ })
51
+ ```
52
+
53
+ ## Authentication
54
+
55
+ Every request carries your tenant API key. The canonical production header is
56
+ **`X-Goable-Key`**, which the client sends automatically:
57
+
58
+ ```ts
59
+ new GoableClient({ apiKey: "gk_…" }) // → sends "X-Goable-Key: gk_…"
60
+ ```
61
+
62
+ > The API also accepts `Authorization: Bearer <key>` as a legacy fallback for
63
+ > direct testing, but new integrations should use the default `X-Goable-Key`
64
+ > path. (Production traffic sits behind CloudFront, which reserves the
65
+ > `Authorization` header for its own signature — the custom header sidesteps
66
+ > that.)
67
+
68
+ Mint a key from the tenant portal at
69
+ [console.goable.io/portal/keys](https://console.goable.io/portal/keys).
70
+
35
71
  ## Configuration
36
72
 
37
73
  ```ts
38
74
  new GoableClient({
39
- apiKey: "…", // required — sent as Authorization: Bearer
75
+ apiKey: "…", // required — sent as X-Goable-Key
40
76
  baseUrl: "https://api.goable.io", // default
41
- timeoutMs: 30_000, // default; 0 disables
42
- fetch: customFetch, // default globalThis.fetch (inject for Node <18 / tests)
77
+ timeoutMs: 30_000, // default; 0 disables
78
+ fetch: customFetch, // default globalThis.fetch (inject for Node <18 / tests)
43
79
  })
44
80
  ```
45
81
 
@@ -53,6 +89,7 @@ new GoableClient({
53
89
  | `scoreHistorical(input)` | `POST /v1/score/historical` | climatology percentiles (Pro+) |
54
90
  | `scorePortfolio(input)` | `POST /v1/score/portfolio` | multi-spot joint variance |
55
91
  | `explainCounterfactual(input)` | `POST /v1/score/explain-counterfactual` | binding constraint, sensitivities, best window/spot |
92
+ | `recommendSpot(input)` | `POST /v1/recommend-spot` | inverse query: top-K ranked sub-spots |
56
93
  | `decision(input)` | `POST /v1/decision` | personalized go/no-go (Pro+) |
57
94
  | `deleteUserData(pseudonym)` | `DELETE /v1/decision/user-data/:p` | GDPR erasure; returns receipt headers |
58
95
  | `explain(input)` / `briefing(input)` | `POST /v1/intelligence/*` | LLM narratives (Pro+) |
@@ -60,6 +97,8 @@ new GoableClient({
60
97
  | `quote(input)` | `POST /v1/underwriting/quote` | parametric premium (Scale) |
61
98
  | `health()` | `GET /v1/health` | liveness |
62
99
 
100
+ Full endpoint reference: [goable.io/docs](https://goable.io/docs).
101
+
63
102
  ## Errors
64
103
 
65
104
  ```ts
@@ -69,8 +108,8 @@ try {
69
108
  await goable.score({ activity: "kitesurfing", location: { lat: 43.7, lng: 7.27 }, ensemble: true })
70
109
  } catch (err) {
71
110
  if (err instanceof GoableApiError) {
72
- err.status // 402
73
- err.code // "PAYMENT_REQUIRED"
111
+ err.status // e.g. 402
112
+ err.code // e.g. "PAYMENT_REQUIRED"
74
113
  err.issues // Zod issues on 422 VALIDATION_ERROR
75
114
  err.detail // free-form context (e.g. plan info)
76
115
  } else if (err instanceof GoableNetworkError) {
@@ -82,40 +121,23 @@ try {
82
121
  ## Browser use
83
122
 
84
123
  The client runs in the browser, but **API keys are secrets** — prefer
85
- server-side use. For direct browser calls the API's CORS allowlist must
86
- include your origin (contact Goable to allowlist it).
124
+ server-side use. For direct browser calls the API's CORS allowlist must include
125
+ your origin (contact Goable to allowlist it).
87
126
 
88
127
  ## Types are generated from the API contract
89
128
 
90
- The request/response types are **generated** from the API's OpenAPI
91
- document (`apps/api/src/openapi/spec.ts`) via
92
- [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) —
93
- they are never hand-authored, so they can't drift from the server. When the
94
- API contract changes, a maintainer runs:
95
-
96
- ```bash
97
- pnpm --filter @goable-io/sdk gen
98
- ```
99
-
100
- which rewrites `src/generated/api.ts` + `openapi.json`. A test
101
- (`generatedFresh.test.ts`) fails in CI if the committed output is stale, so
102
- the types are always in lock-step with the contract. Adding a brand-new
103
- endpoint is the only manual step (a new client method).
129
+ The request/response types are **generated** from the Goable API's OpenAPI
130
+ document (`openapi.json`) via
131
+ [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) — they
132
+ are never hand-authored, so they can't drift from the contract. The committed
133
+ `openapi.json` tracks the live public API; see
134
+ [CONTRIBUTING.md](./CONTRIBUTING.md) for how it stays in sync.
104
135
 
105
- Request types are precise (the API rejects unknown fields). Some response
106
- bodies are modelled as open objects where the endpoint returns a rich,
107
- documented payload — the full wire payload is always returned, and these
108
- tighten automatically as the OpenAPI response schemas are enriched.
136
+ ## Contributing & releases
109
137
 
110
- ## Release (maintainers)
138
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). Releases are automated: merging to
139
+ `main` publishes to npm with provenance.
111
140
 
112
- Publishing is automated by `.github/workflows/sdk-release.yml` (on changes
113
- to `packages/sdk/**` on `main`): it builds, runs the freshness guard +
114
- tests, resolves the next version, prints `npm pack --dry-run` as a leak
115
- check, and `pnpm publish --access public` using the org `NPM_TOKEN`. The
116
- private engine never leaves the repo — only this package's `files` are
117
- published. `publishConfig` repoints `main`/`types`/`exports` at `dist/` at
118
- publish time, so in-repo development keeps using the TS sources.
141
+ ## License
119
142
 
120
- > No npm provenance: it requires a public source repo, and the SDK source
121
- > stays in this private monorepo by design.
143
+ MIT © Fabio Carucci
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * GENERATED FILE — do not edit by hand.
3
- * Source: apps/api/src/openapi/spec.ts (buildOpenApiSpec).
4
- * Regenerate: pnpm --filter @goable-io/sdk gen
3
+ * Source: openapi.json (the committed Goable API contract).
4
+ * Regenerate: pnpm gen
5
5
  */
6
6
  export interface paths {
7
7
  "/v1/health": {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * GENERATED FILE — do not edit by hand.
3
- * Source: apps/api/src/openapi/spec.ts (buildOpenApiSpec).
4
- * Regenerate: pnpm --filter @goable-io/sdk gen
3
+ * Source: openapi.json (the committed Goable API contract).
4
+ * Regenerate: pnpm gen
5
5
  */
6
6
  export {};
7
7
  //# sourceMappingURL=api.js.map
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@goable-io/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript client for the Goable API — suitability scoring for outdoor activities.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "license": "MIT",
8
- "homepage": "https://goable.io",
8
+ "author": "Fabio Carucci <contact@fabio-carucci.com>",
9
+ "homepage": "https://github.com/goable-io/sdk#readme",
9
10
  "repository": {
10
11
  "type": "git",
11
- "url": "https://github.com/goable-io/goable",
12
- "directory": "packages/sdk"
12
+ "url": "git+https://github.com/goable-io/sdk.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/goable-io/sdk/issues"
13
16
  },
14
17
  "keywords": [
15
18
  "goable",
@@ -30,28 +33,28 @@
30
33
  "default": "./dist/index.js"
31
34
  }
32
35
  },
33
- "files": [
34
- "dist",
35
- "src",
36
- "README.md"
37
- ],
36
+ "files": ["dist", "src", "README.md", "LICENSE"],
38
37
  "engines": {
39
38
  "node": ">=18"
40
39
  },
40
+ "packageManager": "pnpm@11.3.0",
41
41
  "publishConfig": {
42
- "access": "public"
42
+ "access": "public",
43
+ "provenance": true
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run --passWithNoTests",
49
+ "gen": "tsx scripts/gen.ts",
50
+ "prepublishOnly": "tsc",
51
+ "clean": "rm -rf dist *.tsbuildinfo"
43
52
  },
44
53
  "devDependencies": {
54
+ "@types/node": "^22.10.0",
45
55
  "openapi-typescript": "^7.13.0",
46
56
  "tsx": "^4.19.2",
47
57
  "typescript": "^5.7.2",
48
58
  "vitest": "^2.1.8"
49
- },
50
- "scripts": {
51
- "build": "tsc -b",
52
- "typecheck": "tsc --noEmit",
53
- "test": "vitest run --passWithNoTests",
54
- "gen": "tsx scripts/gen.ts",
55
- "clean": "rm -rf dist .turbo *.tsbuildinfo"
56
59
  }
57
- }
60
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * GENERATED FILE — do not edit by hand.
3
- * Source: apps/api/src/openapi/spec.ts (buildOpenApiSpec).
4
- * Regenerate: pnpm --filter @goable-io/sdk gen
3
+ * Source: openapi.json (the committed Goable API contract).
4
+ * Regenerate: pnpm gen
5
5
  */
6
6
 
7
7
  export interface paths {