@glboost/sdk 0.0.0-stage → 0.1.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 Chase Holdren
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,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # @glboost/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed client for the [GLBoost](https://glboost.com) API: upload glTF/GLB models, wait for the
4
+ optimized variants, mint signed CDN URLs, and call every other endpoint with the types generated from
5
+ the API's OpenAPI document. Works in Node 20+ and browsers.
6
+
7
+ ```sh
8
+ npm install @glboost/sdk
9
+ ```
10
+
11
+ ```ts
12
+ import { createClient, uploadModel, waitForReady, assetUrl } from '@glboost/sdk';
13
+ import { readFile } from 'node:fs/promises';
14
+
15
+ const client = createClient({ apiKey: process.env.GLBOOST_API_KEY! });
16
+
17
+ const { assetId } = await uploadModel(client, {
18
+ name: 'chair.glb',
19
+ data: await readFile('chair.glb'),
20
+ });
21
+ const ready = await waitForReady(client, assetId);
22
+ console.log(ready.variants.map((v) => [v.preset, v.bytes, v.score?.grade]));
23
+ console.log(assetUrl(assetId, { preset: 'gpu' })); // https://cdn.glboost.com/<id>.glb?p=gpu
24
+ ```
25
+
26
+ Every endpoint is on `client.api` (an [openapi-fetch](https://openapi-ts.dev/openapi-fetch/) client):
27
+
28
+ ```ts
29
+ const { data, error } = await client.api.GET('/api/v1/assets', {
30
+ params: { query: { q: 'chair' } },
31
+ });
32
+ ```
33
+
34
+ Errors from the helpers are `GLBoostApiError` instances; `error.isPlanLimit` is true for 402 and 413,
35
+ with `plan`, `used`, `limit`, and `upgradeUrl` filled in.
36
+
37
+ For tests, `@glboost/sdk/testing` exports `createFakeApi()`, an in-memory stand-in for the API and
38
+ the CDN upload endpoints: `createClient({ fetch: createFakeApi().fetch })`.
39
+
40
+ Docs: https://glboost.com/docs/sdk
@@ -0,0 +1,159 @@
1
+ // ../core/src/spec.ts
2
+ var GEO_VALUES = ["meshopt", "draco", "none", "raw"];
3
+ var TEX_VALUES = ["ktx2", "webp", "avif", "jpeg", "png", "original"];
4
+ var MAXTEX_VALUES = [256, 512, 1024, 2048, 4096];
5
+ var Q_VALUES = ["low", "med", "high"];
6
+ var ANIM_VALUES = ["keep", "strip"];
7
+ var PRESETS = {
8
+ compat: { geo: "none", tex: "jpeg", maxtex: 2048, q: "med", anim: "keep" },
9
+ web: { geo: "meshopt", tex: "webp", maxtex: 2048, q: "med", anim: "keep" },
10
+ gpu: { geo: "meshopt", tex: "ktx2", maxtex: 2048, q: "med", anim: "keep" },
11
+ mobile: { geo: "meshopt", tex: "ktx2", maxtex: 1024, q: "low", anim: "keep", simplify: 0.75 },
12
+ embedded: { geo: "raw", tex: "webp", maxtex: 512, q: "low", anim: "keep", simplify: 0.5 }
13
+ };
14
+ var PRESET_NAMES = [
15
+ "compat",
16
+ "web",
17
+ "gpu",
18
+ "mobile",
19
+ "embedded",
20
+ "original"
21
+ ];
22
+ var DEFAULT_VARIANT_PRESETS = ["compat", "gpu", "original"];
23
+ function isPresetName(value) {
24
+ return typeof value === "string" && PRESET_NAMES.includes(value);
25
+ }
26
+ function presetSpec(name) {
27
+ if (name === "original") return null;
28
+ return { ...PRESETS[name] };
29
+ }
30
+ var SPEC_KEYS = [
31
+ "geo",
32
+ "tex",
33
+ "maxtex",
34
+ "ntex",
35
+ "palette",
36
+ "q",
37
+ "anim",
38
+ "simplify"
39
+ ];
40
+ var FIELD_ORDER = [...SPEC_KEYS].sort();
41
+ function formatSimplify(value) {
42
+ return String(Math.round(value * 100) / 100);
43
+ }
44
+ function specValueString(key, value) {
45
+ if (key === "palette") return "1";
46
+ if (key === "simplify") return formatSimplify(value);
47
+ return String(value);
48
+ }
49
+ function isDefaultValue(spec, key) {
50
+ const value = spec[key];
51
+ if (value === void 0 || value === false) return true;
52
+ return key === "ntex" && value === spec.maxtex;
53
+ }
54
+ function canonicalSpec(spec) {
55
+ const parts = [];
56
+ for (const key of FIELD_ORDER) {
57
+ if (isDefaultValue(spec, key)) continue;
58
+ parts.push(`${key}=${specValueString(key, spec[key])}`);
59
+ }
60
+ return parts.join(",");
61
+ }
62
+ function specDiffFromCompat(spec) {
63
+ const defaults = PRESETS.compat;
64
+ const out = [];
65
+ for (const key of SPEC_KEYS) {
66
+ if (isDefaultValue(spec, key)) continue;
67
+ if (spec[key] === defaults[key]) continue;
68
+ out.push([key, specValueString(key, spec[key])]);
69
+ }
70
+ return out;
71
+ }
72
+ function parseCanonicalSpec(canonical) {
73
+ const raw = {};
74
+ for (const part of canonical.split(",")) {
75
+ const eq = part.indexOf("=");
76
+ if (eq <= 0) throw new Error(`invalid spec part "${part}"`);
77
+ const key = part.slice(0, eq);
78
+ if (key in raw) throw new Error(`duplicate spec key "${key}"`);
79
+ raw[key] = part.slice(eq + 1);
80
+ }
81
+ const spec = specFromStrings(raw);
82
+ if (!spec.ok) throw new Error(spec.error);
83
+ const re = canonicalSpec(spec.spec);
84
+ if (re !== canonical) throw new Error(`spec is not canonical: "${canonical}" (expected "${re}")`);
85
+ return spec.spec;
86
+ }
87
+ function specFromStrings(raw, base) {
88
+ const out = base ? { ...base } : {};
89
+ for (const [key, value] of Object.entries(raw)) {
90
+ switch (key) {
91
+ case "geo":
92
+ if (!GEO_VALUES.includes(value)) return bad(key, value);
93
+ out.geo = value;
94
+ break;
95
+ case "tex":
96
+ if (!TEX_VALUES.includes(value)) return bad(key, value);
97
+ out.tex = value;
98
+ break;
99
+ case "q":
100
+ if (!Q_VALUES.includes(value)) return bad(key, value);
101
+ out.q = value;
102
+ break;
103
+ case "anim":
104
+ if (!ANIM_VALUES.includes(value)) return bad(key, value);
105
+ out.anim = value;
106
+ break;
107
+ case "maxtex": {
108
+ const n = Number(value);
109
+ if (!MAXTEX_VALUES.includes(n)) return bad(key, value);
110
+ out.maxtex = n;
111
+ break;
112
+ }
113
+ case "simplify": {
114
+ const n = Number(value);
115
+ if (!/^\d*\.?\d+$/.test(value) || !Number.isFinite(n) || n < 0.1 || n > 1) {
116
+ return bad(key, value);
117
+ }
118
+ const rounded = Math.round(n * 100) / 100;
119
+ if (rounded >= 1) delete out.simplify;
120
+ else out.simplify = rounded;
121
+ break;
122
+ }
123
+ case "ntex": {
124
+ const n = Number(value);
125
+ if (!MAXTEX_VALUES.includes(n)) return bad(key, value);
126
+ out.ntex = n;
127
+ break;
128
+ }
129
+ case "palette":
130
+ if (value === "1") out.palette = true;
131
+ else if (value === "0") delete out.palette;
132
+ else return bad(key, value);
133
+ break;
134
+ default:
135
+ return { ok: false, error: `unknown spec key "${key}"` };
136
+ }
137
+ }
138
+ if (out.ntex !== void 0 && out.ntex === out.maxtex) delete out.ntex;
139
+ if (!out.palette) delete out.palette;
140
+ if (!out.geo || !out.tex || out.maxtex === void 0 || !out.q || !out.anim) {
141
+ return { ok: false, error: "incomplete spec" };
142
+ }
143
+ return { ok: true, spec: out };
144
+ }
145
+ function bad(key, value) {
146
+ return { ok: false, error: `invalid value "${value}" for "${key}"` };
147
+ }
148
+
149
+ export {
150
+ PRESETS,
151
+ PRESET_NAMES,
152
+ DEFAULT_VARIANT_PRESETS,
153
+ isPresetName,
154
+ presetSpec,
155
+ canonicalSpec,
156
+ specDiffFromCompat,
157
+ parseCanonicalSpec
158
+ };
159
+ //# sourceMappingURL=chunk-YL4SZ5Y4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/spec.ts"],"sourcesContent":["export const GEO_VALUES = ['meshopt', 'draco', 'none', 'raw'] as const;\nexport type Geo = (typeof GEO_VALUES)[number];\nexport const TEX_VALUES = ['ktx2', 'webp', 'avif', 'jpeg', 'png', 'original'] as const;\nexport type Tex = (typeof TEX_VALUES)[number];\nexport const MAXTEX_VALUES = [256, 512, 1024, 2048, 4096] as const;\nexport type MaxTex = (typeof MAXTEX_VALUES)[number];\nexport const Q_VALUES = ['low', 'med', 'high'] as const;\nexport type Q = (typeof Q_VALUES)[number];\nexport const ANIM_VALUES = ['keep', 'strip'] as const;\nexport type Anim = (typeof ANIM_VALUES)[number];\n\nexport interface OptimizeSpec {\n geo: Geo;\n tex: Tex;\n maxtex: MaxTex;\n q: Q;\n anim: Anim;\n /** Meshopt simplifier target ratio in [0.1, 1). Omitted = no simplification. */\n simplify?: number;\n /** Cap for data-slot textures (normal, occlusion, metallicRoughness). Omitted = same as `maxtex`. */\n ntex?: MaxTex;\n /** Merge texture-less materials into one palette texture. Omitted = off. */\n palette?: boolean;\n}\n\nexport const PRESETS = {\n compat: { geo: 'none', tex: 'jpeg', maxtex: 2048, q: 'med', anim: 'keep' },\n web: { geo: 'meshopt', tex: 'webp', maxtex: 2048, q: 'med', anim: 'keep' },\n gpu: { geo: 'meshopt', tex: 'ktx2', maxtex: 2048, q: 'med', anim: 'keep' },\n mobile: { geo: 'meshopt', tex: 'ktx2', maxtex: 1024, q: 'low', anim: 'keep', simplify: 0.75 },\n embedded: { geo: 'raw', tex: 'webp', maxtex: 512, q: 'low', anim: 'keep', simplify: 0.5 },\n} as const satisfies Record<string, OptimizeSpec>;\n\nexport type NamedPreset = keyof typeof PRESETS;\nexport type PresetName = NamedPreset | 'original';\nexport const PRESET_NAMES: readonly PresetName[] = [\n 'compat',\n 'web',\n 'gpu',\n 'mobile',\n 'embedded',\n 'original',\n];\nexport const DEFAULT_PRESET: PresetName = 'compat';\n/** Variants created automatically for every uploaded version. */\nexport const DEFAULT_VARIANT_PRESETS: readonly PresetName[] = ['compat', 'gpu', 'original'];\n\nexport function isPresetName(value: unknown): value is PresetName {\n return typeof value === 'string' && (PRESET_NAMES as readonly string[]).includes(value);\n}\n\nexport function presetSpec(name: PresetName): OptimizeSpec | null {\n if (name === 'original') return null;\n return { ...PRESETS[name] };\n}\n\n/**\n * Every grammar key in URL spelling order. The canonical string sorts the same keys\n * alphabetically. Delivery URLs, signed URLs, and the dashboard all derive from this list, so a new\n * key is added here once.\n */\nexport const SPEC_KEYS = [\n 'geo',\n 'tex',\n 'maxtex',\n 'ntex',\n 'palette',\n 'q',\n 'anim',\n 'simplify',\n] as const;\nexport type SpecKey = (typeof SPEC_KEYS)[number];\n\nconst FIELD_ORDER = [...SPEC_KEYS].sort() as readonly SpecKey[];\n\nfunction formatSimplify(value: number): string {\n return String(Math.round(value * 100) / 100);\n}\n\n/** Spelling of one spec value in URLs and canonical strings (`palette=1`, `simplify=0.5`). */\nexport function specValueString(key: SpecKey, value: NonNullable<OptimizeSpec[SpecKey]>): string {\n if (key === 'palette') return '1';\n if (key === 'simplify') return formatSimplify(value as number);\n return String(value);\n}\n\n/** True when the key sits at its default and is left out of canonical strings and URLs. */\nfunction isDefaultValue(spec: OptimizeSpec, key: SpecKey): boolean {\n const value = spec[key];\n if (value === undefined || value === false) return true;\n return key === 'ntex' && value === spec.maxtex;\n}\n\n/**\n * All fields sorted by key, `key=value` joined by `,`. Optional keys are omitted at their defaults:\n * `simplify` when unset, `palette` when off, `ntex` when it equals `maxtex`.\n */\nexport function canonicalSpec(spec: OptimizeSpec): string {\n const parts: string[] = [];\n for (const key of FIELD_ORDER) {\n if (isDefaultValue(spec, key)) continue;\n parts.push(`${key}=${specValueString(key, spec[key]!)}`);\n }\n return parts.join(',');\n}\n\n/** `[key, value]` pairs where `spec` differs from `compat`, in `SPEC_KEYS` order, for ad-hoc URLs. */\nexport function specDiffFromCompat(spec: OptimizeSpec): [SpecKey, string][] {\n const defaults = PRESETS.compat as Partial<OptimizeSpec>;\n const out: [SpecKey, string][] = [];\n for (const key of SPEC_KEYS) {\n if (isDefaultValue(spec, key)) continue;\n if (spec[key] === defaults[key]) continue;\n out.push([key, specValueString(key, spec[key]!)]);\n }\n return out;\n}\n\n/** Parses a canonical spec string; throws when it is not exactly canonical. */\nexport function parseCanonicalSpec(canonical: string): OptimizeSpec {\n const raw: Record<string, string> = {};\n for (const part of canonical.split(',')) {\n const eq = part.indexOf('=');\n if (eq <= 0) throw new Error(`invalid spec part \"${part}\"`);\n const key = part.slice(0, eq);\n if (key in raw) throw new Error(`duplicate spec key \"${key}\"`);\n raw[key] = part.slice(eq + 1);\n }\n const spec = specFromStrings(raw);\n if (!spec.ok) throw new Error(spec.error);\n const re = canonicalSpec(spec.spec);\n if (re !== canonical) throw new Error(`spec is not canonical: \"${canonical}\" (expected \"${re}\")`);\n return spec.spec;\n}\n\n/** Canonical string with `=` → `-` and `,` → `_`, safe for R2 keys and file names. */\nexport function specSlug(canonical: string): string {\n return canonical.replace(/=/g, '-').replace(/,/g, '_');\n}\n\ntype SpecResult = { ok: true; spec: OptimizeSpec } | { ok: false; error: string };\n\nfunction specFromStrings(raw: Record<string, string>, base?: OptimizeSpec): SpecResult {\n const out: Partial<OptimizeSpec> = base ? { ...base } : {};\n for (const [key, value] of Object.entries(raw)) {\n switch (key) {\n case 'geo':\n if (!(GEO_VALUES as readonly string[]).includes(value)) return bad(key, value);\n out.geo = value as Geo;\n break;\n case 'tex':\n if (!(TEX_VALUES as readonly string[]).includes(value)) return bad(key, value);\n out.tex = value as Tex;\n break;\n case 'q':\n if (!(Q_VALUES as readonly string[]).includes(value)) return bad(key, value);\n out.q = value as Q;\n break;\n case 'anim':\n if (!(ANIM_VALUES as readonly string[]).includes(value)) return bad(key, value);\n out.anim = value as Anim;\n break;\n case 'maxtex': {\n const n = Number(value);\n if (!(MAXTEX_VALUES as readonly number[]).includes(n)) return bad(key, value);\n out.maxtex = n as MaxTex;\n break;\n }\n case 'simplify': {\n const n = Number(value);\n if (!/^\\d*\\.?\\d+$/.test(value) || !Number.isFinite(n) || n < 0.1 || n > 1) {\n return bad(key, value);\n }\n const rounded = Math.round(n * 100) / 100;\n if (rounded >= 1) delete out.simplify;\n else out.simplify = rounded;\n break;\n }\n case 'ntex': {\n const n = Number(value);\n if (!(MAXTEX_VALUES as readonly number[]).includes(n)) return bad(key, value);\n out.ntex = n as MaxTex;\n break;\n }\n case 'palette':\n if (value === '1') out.palette = true;\n else if (value === '0') delete out.palette;\n else return bad(key, value);\n break;\n default:\n return { ok: false, error: `unknown spec key \"${key}\"` };\n }\n }\n // Defaults are never stored, so the canonical string stays the one the Phase 1 grammar produced.\n if (out.ntex !== undefined && out.ntex === out.maxtex) delete out.ntex;\n if (!out.palette) delete out.palette;\n // Every value was checked above; only completeness is left (no zod here, see spec-schema.ts).\n if (!out.geo || !out.tex || out.maxtex === undefined || !out.q || !out.anim) {\n return { ok: false, error: 'incomplete spec' };\n }\n return { ok: true, spec: out as OptimizeSpec };\n}\n\nfunction bad(key: string, value: string): SpecResult {\n return { ok: false, error: `invalid value \"${value}\" for \"${key}\"` };\n}\n\nexport type VariantRequest =\n | { kind: 'original' }\n | {\n kind: 'spec';\n spec: OptimizeSpec;\n canonical: string;\n /** Set when the request names a preset without overrides (lazy generation is allowed). */\n preset: NamedPreset | null;\n };\n\nexport type VariantQuery =\n | { ok: true; request: VariantRequest; version?: string; download: boolean }\n | { ok: false; error: string };\n\nconst VERSION_RE = /^[0-9a-z]{1,32}$/;\n\n/** Parses delivery URL query parameters into a variant request. Unknown keys are ignored. */\nexport function parseVariantQuery(\n params: URLSearchParams,\n defaultPreset: PresetName,\n): VariantQuery {\n const p = params.get('p');\n let presetName: PresetName;\n if (p === null) presetName = defaultPreset;\n else if (isPresetName(p)) presetName = p;\n else return { ok: false, error: `unknown preset \"${p}\"` };\n\n const overrides: Record<string, string> = {};\n for (const key of SPEC_KEYS) {\n const value = params.get(key);\n if (value !== null) overrides[key] = value;\n }\n\n let version: string | undefined;\n const v = params.get('v');\n if (v !== null) {\n if (!VERSION_RE.test(v)) return { ok: false, error: `invalid version \"${v}\"` };\n version = v;\n }\n const download = params.get('dl') === '1';\n\n if (presetName === 'original') {\n if (Object.keys(overrides).length > 0) {\n return { ok: false, error: 'p=original cannot be combined with spec parameters' };\n }\n return { ok: true, request: { kind: 'original' }, version, download };\n }\n\n const result = specFromStrings(overrides, PRESETS[presetName]);\n if (!result.ok) return result;\n const canonical = canonicalSpec(result.spec);\n const preset = canonical === canonicalSpec(PRESETS[presetName]) ? presetName : null;\n return {\n ok: true,\n request: { kind: 'spec', spec: result.spec, canonical, preset },\n version,\n download,\n };\n}\n"],"mappings":"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","names":[]}