@freeholder/sdk 0.0.0-stage → 0.2.0-beta.3
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 +202 -0
- package/README.md +37 -2
- package/dist/generated.d.ts +33601 -0
- package/dist/generated.js +1405 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +110 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +34 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PLATFORM_VERSION } from "./version.ts";
|
|
2
|
+
import type { FreeholderApi, PageableService, ServiceInput, ServiceName, ServiceOutput } from "./generated.ts";
|
|
3
|
+
export { PLATFORM_VERSION };
|
|
4
|
+
export { PAGEABLE_SERVICES, SERVICE_NAMES, type FreeholderApi, type PageableService, type ServiceCatalog, type ServiceInput, type ServiceName, type ServiceOutput, } from "./generated.ts";
|
|
5
|
+
export declare class FreeholderError extends Error {
|
|
6
|
+
readonly status: number;
|
|
7
|
+
readonly code: string;
|
|
8
|
+
constructor(status: number, code: string, message: string);
|
|
9
|
+
}
|
|
10
|
+
export interface FreeholderClientOptions {
|
|
11
|
+
/** Instance origin, no trailing slash. */
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
/** API key from Settings. Omit for public services. */
|
|
14
|
+
token?: string;
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
type OptionalInputService = {
|
|
18
|
+
[K in ServiceName]: Record<string, never> extends ServiceInput<K> ? K : never;
|
|
19
|
+
}[ServiceName];
|
|
20
|
+
type PageItem<K extends PageableService> = ServiceOutput<K> extends {
|
|
21
|
+
rows: Array<infer Item>;
|
|
22
|
+
} ? Item : never;
|
|
23
|
+
export declare class FreeholderClient {
|
|
24
|
+
readonly version = "0.2.0-beta.3";
|
|
25
|
+
private readonly baseUrl;
|
|
26
|
+
private readonly token?;
|
|
27
|
+
private readonly fetchImpl;
|
|
28
|
+
constructor(options: FreeholderClientOptions);
|
|
29
|
+
/** A client that presents a different API key on the same origin. */
|
|
30
|
+
withToken(token: string): FreeholderClient;
|
|
31
|
+
/**
|
|
32
|
+
* Namespaced methods for every service in the published catalog.
|
|
33
|
+
* Instance-specific plugin verbs still go through `call(name, input)`.
|
|
34
|
+
*/
|
|
35
|
+
get api(): FreeholderApi;
|
|
36
|
+
call<K extends OptionalInputService>(name: K, input?: ServiceInput<K>): Promise<ServiceOutput<K>>;
|
|
37
|
+
call<K extends ServiceName>(name: K, input: ServiceInput<K>): Promise<ServiceOutput<K>>;
|
|
38
|
+
call(name: string, input?: unknown): Promise<unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Walk a `{ rows, total }` list until every row is yielded.
|
|
41
|
+
* Only services in `PAGEABLE_SERVICES` accept this wrapper.
|
|
42
|
+
*/
|
|
43
|
+
paginate<K extends PageableService>(name: K, input: Omit<ServiceInput<K>, "offset">): AsyncGenerator<PageItem<K>>;
|
|
44
|
+
private request;
|
|
45
|
+
}
|
|
46
|
+
export declare function createClient(options: FreeholderClientOptions): FreeholderClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copyright (C) 2026 Tony Aly
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Typed HTTP client for a Freeholder instance (MASTER.md §28, C3.03).
|
|
4
|
+
//
|
|
5
|
+
// Concrete methods and input/output types are generated from the live
|
|
6
|
+
// service registry. Transport, auth and pagination wrappers live here and
|
|
7
|
+
// are typed against that generated layer so a contract change breaks them.
|
|
8
|
+
import { PLATFORM_VERSION } from "./version.js";
|
|
9
|
+
export { PLATFORM_VERSION };
|
|
10
|
+
export { PAGEABLE_SERVICES, SERVICE_NAMES, } from "./generated.js";
|
|
11
|
+
const SERVICE_NAME = /^[a-z][a-zA-Z0-9]*\.[a-zA-Z0-9]+$/;
|
|
12
|
+
export class FreeholderError extends Error {
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
constructor(status, code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.name = "FreeholderError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class FreeholderClient {
|
|
23
|
+
version = PLATFORM_VERSION;
|
|
24
|
+
baseUrl;
|
|
25
|
+
token;
|
|
26
|
+
fetchImpl;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
29
|
+
this.token = options.token;
|
|
30
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
31
|
+
}
|
|
32
|
+
/** A client that presents a different API key on the same origin. */
|
|
33
|
+
withToken(token) {
|
|
34
|
+
return new FreeholderClient({
|
|
35
|
+
baseUrl: this.baseUrl,
|
|
36
|
+
token,
|
|
37
|
+
fetch: this.fetchImpl,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Namespaced methods for every service in the published catalog.
|
|
42
|
+
* Instance-specific plugin verbs still go through `call(name, input)`.
|
|
43
|
+
*/
|
|
44
|
+
get api() {
|
|
45
|
+
return createApiProxy((name, input) => this.request(name, input));
|
|
46
|
+
}
|
|
47
|
+
async call(name, input = {}) {
|
|
48
|
+
return this.request(name, input);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Walk a `{ rows, total }` list until every row is yielded.
|
|
52
|
+
* Only services in `PAGEABLE_SERVICES` accept this wrapper.
|
|
53
|
+
*/
|
|
54
|
+
async *paginate(name, input) {
|
|
55
|
+
let offset = 0;
|
|
56
|
+
for (;;) {
|
|
57
|
+
const page = (await this.call(name, { ...input, offset }));
|
|
58
|
+
for (const row of page.rows)
|
|
59
|
+
yield row;
|
|
60
|
+
offset += page.rows.length;
|
|
61
|
+
if (page.rows.length === 0 || offset >= page.total)
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async request(service, input = {}) {
|
|
66
|
+
if (!SERVICE_NAME.test(service)) {
|
|
67
|
+
throw new FreeholderError(400, "validation", `Not a service name: ${service}`);
|
|
68
|
+
}
|
|
69
|
+
const headers = {
|
|
70
|
+
accept: "application/json",
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
};
|
|
73
|
+
if (this.token)
|
|
74
|
+
headers.authorization = `Bearer ${this.token}`;
|
|
75
|
+
const response = await this.fetchImpl(`${this.baseUrl}/api/v1/${service}`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers,
|
|
78
|
+
body: JSON.stringify(input ?? {}),
|
|
79
|
+
});
|
|
80
|
+
const body = (await response.json().catch(() => ({})));
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new FreeholderError(response.status, body.error?.code ?? "internal", body.error?.message ?? `HTTP ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
return body;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function createClient(options) {
|
|
88
|
+
return new FreeholderClient(options);
|
|
89
|
+
}
|
|
90
|
+
function createApiProxy(request) {
|
|
91
|
+
const families = new Map();
|
|
92
|
+
return new Proxy({}, {
|
|
93
|
+
get(_target, family) {
|
|
94
|
+
if (typeof family !== "string" || family === "then")
|
|
95
|
+
return undefined;
|
|
96
|
+
let methods = families.get(family);
|
|
97
|
+
if (!methods) {
|
|
98
|
+
methods = new Proxy({}, {
|
|
99
|
+
get(_inner, verb) {
|
|
100
|
+
if (typeof verb !== "string" || verb === "then")
|
|
101
|
+
return undefined;
|
|
102
|
+
return (input = {}) => request(`${family}.${verb}`, input);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
families.set(family, methods);
|
|
106
|
+
}
|
|
107
|
+
return methods;
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLATFORM_VERSION = "0.2.0-beta.3";
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freeholder/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-beta.3",
|
|
4
|
+
"description": "Typed HTTP client for a Freeholder instance, generated from the live service registry",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Tony Aly <tony@paradisemodern.com> (https://tonyaly.com)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/CampDenman/freeholder.git",
|
|
10
|
+
"directory": "packages/sdk"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20.19"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"test": "node --test test/*.test.mjs"
|
|
36
|
+
}
|
|
6
37
|
}
|