@slytrunk/sly-tifo-client 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/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ // src/refusal.ts
2
+ function splitRefusal(reason) {
3
+ const at = reason.indexOf(":");
4
+ if (at === -1) return { code: reason, detail: "" };
5
+ return { code: reason.slice(0, at).trim(), detail: reason.slice(at + 1).trim() };
6
+ }
7
+ function isRefused(body) {
8
+ const shape = body;
9
+ return shape?.accepted === false && typeof shape.reason === "string";
10
+ }
11
+ function asRefused(body) {
12
+ const { code, detail } = splitRefusal(body.reason);
13
+ return { accepted: false, reason: body.reason, code, detail };
14
+ }
15
+
16
+ // src/tokens.ts
17
+ var REFRESH_MARGIN_MS = 6e4;
18
+ function createTokenHolder(opts) {
19
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
20
+ let held = null;
21
+ let inFlight = null;
22
+ const alive = (token) => {
23
+ const expires = Date.parse(token.expiresAt);
24
+ if (Number.isNaN(expires)) return false;
25
+ return expires - now().getTime() > REFRESH_MARGIN_MS;
26
+ };
27
+ return {
28
+ async current() {
29
+ if (held !== null && alive(held)) return held.token;
30
+ if (inFlight === null) {
31
+ inFlight = opts.getToken().finally(() => {
32
+ inFlight = null;
33
+ });
34
+ }
35
+ held = await inFlight;
36
+ return held.token;
37
+ },
38
+ invalidate() {
39
+ held = null;
40
+ }
41
+ };
42
+ }
43
+
44
+ // src/transport.ts
45
+ var ApiError = class extends Error {
46
+ status;
47
+ code;
48
+ // `options` is the standard `ErrorOptions`, optional so every existing
49
+ // three-argument call keeps compiling. Its `cause` is the error that made
50
+ // this one — for `unreachable`, the fetch failure the message rewrites.
51
+ constructor(message, status, code, options) {
52
+ super(message, options);
53
+ this.name = "ApiError";
54
+ this.status = status;
55
+ this.code = code;
56
+ }
57
+ };
58
+ async function send(opts) {
59
+ let response;
60
+ try {
61
+ response = await opts.fetch(`${opts.apiBase}${opts.path}`, {
62
+ method: opts.method,
63
+ headers: {
64
+ authorization: `Bearer ${opts.token}`,
65
+ ...opts.body === void 0 ? {} : { "content-type": "application/json" },
66
+ ...opts.headers ?? {}
67
+ },
68
+ ...opts.body === void 0 ? {} : { body: JSON.stringify(opts.body) }
69
+ });
70
+ } catch (cause) {
71
+ throw new ApiError(
72
+ `The server at ${opts.apiBase} could not be reached. Check your connection and try again.`,
73
+ 0,
74
+ "unreachable",
75
+ { cause }
76
+ );
77
+ }
78
+ const text = await response.text();
79
+ let parsed = void 0;
80
+ try {
81
+ parsed = text === "" ? void 0 : JSON.parse(text);
82
+ } catch {
83
+ }
84
+ if (!response.ok) {
85
+ const shape = parsed;
86
+ const message = typeof shape?.message === "string" ? shape.message : `The request failed (${response.status}).`;
87
+ const code = typeof shape?.error === "string" ? shape.error : "http-error";
88
+ throw new ApiError(message, response.status, code);
89
+ }
90
+ return parsed;
91
+ }
92
+
93
+ // src/client.ts
94
+ function createClient(opts) {
95
+ const doFetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
96
+ const apiBase = opts.apiBase.replace(/\/+$/, "");
97
+ const tokens = createTokenHolder({
98
+ getToken: opts.getToken,
99
+ ...opts.now === void 0 ? {} : { now: opts.now }
100
+ });
101
+ const call = async (method, path, body, headers) => {
102
+ const attempt = async () => send({
103
+ fetch: doFetch,
104
+ apiBase,
105
+ method,
106
+ path,
107
+ token: await tokens.current(),
108
+ ...body === void 0 ? {} : { body },
109
+ ...headers === void 0 ? {} : { headers }
110
+ });
111
+ try {
112
+ return await attempt();
113
+ } catch (error) {
114
+ if (error instanceof ApiError && error.status === 401) {
115
+ tokens.invalidate();
116
+ return attempt();
117
+ }
118
+ throw error;
119
+ }
120
+ };
121
+ return {
122
+ async me() {
123
+ const body = await call("GET", "/v1/me");
124
+ return isRefused(body) ? asRefused(body) : body;
125
+ },
126
+ async award(input) {
127
+ const body = await call(
128
+ "POST",
129
+ "/v1/events",
130
+ {
131
+ action: input.action,
132
+ ...input.payload === void 0 ? {} : { payload: input.payload },
133
+ ...input.subject === void 0 ? {} : { subject: input.subject }
134
+ },
135
+ input.idempotencyKey === void 0 ? void 0 : { "idempotency-key": input.idempotencyKey }
136
+ );
137
+ return isRefused(body) ? asRefused(body) : body;
138
+ },
139
+ // All three unwrap an envelope. The API answers `{ collectibles: [...] }`,
140
+ // not a bare array; the SDK returns the array, because an envelope is a
141
+ // transport detail and every caller would immediately index past it.
142
+ async collectibles() {
143
+ const body = await call("GET", "/v1/collectibles");
144
+ return body.collectibles;
145
+ },
146
+ async collections() {
147
+ const body = await call("GET", "/v1/collections");
148
+ return body.collections;
149
+ },
150
+ async actions() {
151
+ const body = await call("GET", "/v1/actions");
152
+ return body.actions;
153
+ }
154
+ };
155
+ }
156
+
157
+ // src/environments.ts
158
+ var API_BASES = {
159
+ production: "https://api.slytifo.com",
160
+ stage: "https://api.stage-slytifo.com"
161
+ };
162
+ export {
163
+ API_BASES,
164
+ ApiError,
165
+ createClient,
166
+ isRefused,
167
+ splitRefusal
168
+ };
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/refusal.ts","../src/tokens.ts","../src/transport.ts","../src/client.ts","../src/environments.ts"],"sourcesContent":["/**\n * A refusal that arrived as HTTP 200.\n *\n * `{ accepted: false, reason: \"cap-exhausted: …\" }` is a 200 because a daily\n * cap, a closed season or a half-built ladder is ordinary program state rather\n * than a fault in the request. A generated client reports SUCCESS for this\n * unless the caller reads `accepted` — which is the single most common way to\n * misuse this API, and the reason this package makes it a typed value rather\n * than a documented footnote.\n *\n * The reason is split so a caller can branch on a stable code without string\n * matching at every call site. `apps/api` writes it as `code: detail`; an\n * unprefixed reason is all code and no detail.\n */\nexport type Refused = {\n accepted: false\n reason: string\n code: string\n detail: string\n}\n\nexport function splitRefusal(reason: string): { code: string; detail: string } {\n const at = reason.indexOf(':')\n if (at === -1) return { code: reason, detail: '' }\n return { code: reason.slice(0, at).trim(), detail: reason.slice(at + 1).trim() }\n}\n\n/** Narrow a 200 body that may be either an answer or a refusal. */\nexport function isRefused(body: unknown): body is { accepted: false; reason: string } {\n const shape = body as { accepted?: unknown; reason?: unknown } | null\n return shape?.accepted === false && typeof shape.reason === 'string'\n}\n\nexport function asRefused(body: { accepted: false; reason: string }): Refused {\n const { code, detail } = splitRefusal(body.reason)\n return { accepted: false, reason: body.reason, code, detail }\n}\n","/**\n * The end-user token's fifteen minutes, handled here so no caller has to.\n *\n * THE IN-FLIGHT PROMISE IS NOT AN OPTIMIZATION. A page that mounts three\n * widgets calls this three times in the same tick; without the shared promise\n * that is three mints, and under GoTrue-style single-live-credential semantics\n * the later ones can invalidate the earlier. Sharing the promise makes a burst\n * of callers one mint.\n *\n * The sixty-second margin is refresh-AHEAD, not refresh-on-failure. A token\n * that passes the check here can still be rejected — clock skew, a token\n * revoked server-side — which is why `client.ts` ALSO retries once on a 401.\n * The two are belt and braces and neither replaces the other.\n */\nconst REFRESH_MARGIN_MS = 60_000\n\nexport type MintedToken = { token: string; expiresAt: string }\nexport type GetToken = () => Promise<MintedToken>\n\nexport type TokenHolder = {\n current(): Promise<string>\n invalidate(): void\n}\n\nexport function createTokenHolder(opts: { getToken: GetToken; now?: () => Date }): TokenHolder {\n const now = opts.now ?? (() => new Date())\n let held: MintedToken | null = null\n let inFlight: Promise<MintedToken> | null = null\n\n const alive = (token: MintedToken): boolean => {\n const expires = Date.parse(token.expiresAt)\n // An unparseable expiry is treated as already dead rather than as\n // permanently alive: failing toward an extra mint is recoverable, failing\n // toward a token that is never refreshed is a page that breaks after\n // fifteen minutes with no error.\n if (Number.isNaN(expires)) return false\n return expires - now().getTime() > REFRESH_MARGIN_MS\n }\n\n return {\n async current(): Promise<string> {\n if (held !== null && alive(held)) return held.token\n if (inFlight === null) {\n inFlight = opts.getToken().finally(() => {\n inFlight = null\n })\n }\n held = await inFlight\n return held.token\n },\n invalidate(): void {\n held = null\n },\n }\n}\n","/**\n * One request, one place.\n *\n * The rules here are lifted from the admin app's API client, which\n * solved the same problem for the admin's session credential: read the body\n * BEFORE branching on `ok`, because this API writes its message on the failure\n * paths; throw the server's sentence rather than a status, because the sentence\n * is written for a person to act on; and replace `TypeError: fetch failed` with\n * something that names the service, because a JavaScript builtin's name is not\n * a next step.\n *\n * What is NOT here, deliberately: retries. The token-aware layer in\n * `client.ts` retries exactly once on a 401 after refreshing, and that is the\n * only retry this package performs.\n */\nexport type Fetch = typeof globalThis.fetch\n\nexport class ApiError extends Error {\n readonly status: number\n readonly code: string\n\n // `options` is the standard `ErrorOptions`, optional so every existing\n // three-argument call keeps compiling. Its `cause` is the error that made\n // this one — for `unreachable`, the fetch failure the message rewrites.\n constructor(message: string, status: number, code: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'ApiError'\n this.status = status\n this.code = code\n }\n}\n\nexport async function send(opts: {\n fetch: Fetch\n apiBase: string\n method: 'GET' | 'POST'\n path: string\n token: string\n body?: unknown\n headers?: Record<string, string>\n}): Promise<unknown> {\n let response: Response\n try {\n response = await opts.fetch(`${opts.apiBase}${opts.path}`, {\n method: opts.method,\n headers: {\n authorization: `Bearer ${opts.token}`,\n ...(opts.body === undefined ? {} : { 'content-type': 'application/json' }),\n ...(opts.headers ?? {}),\n },\n ...(opts.body === undefined ? {} : { body: JSON.stringify(opts.body) }),\n })\n } catch (cause) {\n // The message names the service; `cause` keeps the builtin's error, which\n // is the only record of WHY. \"Illegal invocation\" is an unbound fetch in\n // workerd, not a network outage, and they read identically without it.\n throw new ApiError(\n `The server at ${opts.apiBase} could not be reached. Check your connection and try again.`,\n 0,\n 'unreachable',\n { cause },\n )\n }\n\n const text = await response.text()\n let parsed: unknown = undefined\n try {\n parsed = text === '' ? undefined : JSON.parse(text)\n } catch {\n // Not JSON — a proxy's HTML error page, or the service being down. Fall\n // through to the status-shaped message rather than throwing a SyntaxError,\n // which would name a parser and not the problem.\n }\n\n if (!response.ok) {\n const shape = parsed as { message?: unknown; error?: unknown } | undefined\n const message =\n typeof shape?.message === 'string'\n ? shape.message\n : `The request failed (${response.status}).`\n const code = typeof shape?.error === 'string' ? shape.error : 'http-error'\n throw new ApiError(message, response.status, code)\n }\n return parsed\n}\n","import type { operations } from './generated/openapi'\nimport { asRefused, isRefused, type Refused } from './refusal'\nimport { createTokenHolder, type GetToken } from './tokens'\nimport { ApiError, send, type Fetch } from './transport'\n\n/** The 200 JSON body of one operation. */\ntype Json200<Op> = Op extends {\n responses: { 200: { content: { 'application/json': infer T } } }\n}\n ? T\n : never\n\n// `getMyStanding`'s 200 is a union: the standing arm has no `accepted` key at\n// all, and the refusal arm is `{ accepted: false; reason: string }`. Exclude\n// rather than Extract, because the standing arm has no discriminant of its own.\nexport type Standing = Exclude<Json200<operations['getMyStanding']>, { accepted: false }>\n\n// `awardEvent`'s arms keep `accepted: true` / `accepted: false` as LITERAL\n// types, so this one discriminates directly.\nexport type AwardAccepted = Extract<Json200<operations['awardEvent']>, { accepted: true }>\n\n// All three catalog routes are ENVELOPED. The client unwraps them so the SDK\n// surface is a flat array.\nexport type CatalogCollectible = Json200<operations['listCollectibles']>['collectibles'][number]\nexport type Collection = Json200<operations['listCollections']>['collections'][number]\nexport type CatalogAction = Json200<operations['listActions']>['actions'][number]\n\nexport type AwardInput = {\n action: string\n payload?: unknown\n subject?: string\n /** Sent as the `Idempotency-Key` HEADER. The API has never read a body field of that name. */\n idempotencyKey?: string\n}\n\nexport type ClientOptions = {\n apiBase: string\n /**\n * Where a token comes from. The client NEVER sees a secret key — this is the\n * property that makes the package publishable, and a caller wiring a secret\n * key in here has defeated the whole design.\n */\n getToken: GetToken\n fetch?: Fetch\n now?: () => Date\n}\n\nexport type SlyClient = {\n me(): Promise<Standing | Refused>\n award(input: AwardInput): Promise<AwardAccepted | Refused>\n collectibles(): Promise<CatalogCollectible[]>\n collections(): Promise<Collection[]>\n actions(): Promise<CatalogAction[]>\n}\n\nexport function createClient(opts: ClientOptions): SlyClient {\n // `fetch.bind(globalThis)`, not the bare reference: workerd's global `fetch`\n // throws \"Illegal invocation\" the moment it is called as an object property\n // rather than on the global scope. The testbed Worker found this the hard\n // way, and this package is destined for exactly that runtime.\n const doFetch = opts.fetch ?? globalThis.fetch.bind(globalThis)\n // A pasted base often ends in `/`, and `${apiBase}${path}` would then ask for\n // `//v1/me` — a 404 that reads like a missing route.\n const apiBase = opts.apiBase.replace(/\\/+$/, '')\n const tokens = createTokenHolder({\n getToken: opts.getToken,\n ...(opts.now === undefined ? {} : { now: opts.now }),\n })\n\n /**\n * ONE retry, and only on a 401.\n *\n * A 401 that survives a fresh token is a real refusal — a revoked key, a\n * token for an end user that no longer exists — and retrying it forever is\n * how a demo becomes a hot spin in front of a customer. Every other status\n * is returned as it came: no new token fixes a 422.\n */\n const call = async (\n method: 'GET' | 'POST',\n path: string,\n body?: unknown,\n headers?: Record<string, string>,\n ): Promise<unknown> => {\n const attempt = async (): Promise<unknown> =>\n send({\n fetch: doFetch,\n apiBase,\n method,\n path,\n token: await tokens.current(),\n ...(body === undefined ? {} : { body }),\n ...(headers === undefined ? {} : { headers }),\n })\n\n try {\n return await attempt()\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) {\n tokens.invalidate()\n return attempt()\n }\n throw error\n }\n }\n\n return {\n async me(): Promise<Standing | Refused> {\n const body = await call('GET', '/v1/me')\n return isRefused(body) ? asRefused(body) : (body as Standing)\n },\n\n async award(input: AwardInput): Promise<AwardAccepted | Refused> {\n const body = await call(\n 'POST',\n '/v1/events',\n {\n action: input.action,\n ...(input.payload === undefined ? {} : { payload: input.payload }),\n ...(input.subject === undefined ? {} : { subject: input.subject }),\n },\n input.idempotencyKey === undefined\n ? undefined\n : { 'idempotency-key': input.idempotencyKey },\n )\n return isRefused(body) ? asRefused(body) : (body as AwardAccepted)\n },\n\n // All three unwrap an envelope. The API answers `{ collectibles: [...] }`,\n // not a bare array; the SDK returns the array, because an envelope is a\n // transport detail and every caller would immediately index past it.\n async collectibles(): Promise<CatalogCollectible[]> {\n const body = (await call('GET', '/v1/collectibles')) as { collectibles: CatalogCollectible[] }\n return body.collectibles\n },\n\n async collections(): Promise<Collection[]> {\n const body = (await call('GET', '/v1/collections')) as { collections: Collection[] }\n return body.collections\n },\n\n async actions(): Promise<CatalogAction[]> {\n const body = (await call('GET', '/v1/actions')) as { actions: CatalogAction[] }\n return body.actions\n },\n }\n}\n","/**\n * The two hosted API origins, so nobody hand-types a hostname.\n *\n * Deliberately NOT an `environment: 'stage'` option on `createClient`: a\n * customer behind their own proxy needs the raw base anyway, and an option\n * would hide which host is being called. And the environment is decided by\n * the KEY, not by this: a token minted with a production key and sent to stage\n * answers 401, retried once, then thrown as `ApiError(401)`.\n */\nexport const API_BASES = {\n production: 'https://api.slytifo.com',\n stage: 'https://api.stage-slytifo.com',\n} as const\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@slytrunk/sly-tifo-client",
3
+ "version": "0.1.0",
4
+ "description": "Typed client for the Sly Tifo loyalty API.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/SlyTrunk/sly-tifo.git",
9
+ "directory": "packages/client"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ }
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "prepack": "tsup",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "generate:types": "node scripts/generate-types.mjs"
41
+ },
42
+ "devDependencies": {
43
+ "jsdom": "30.0.1",
44
+ "openapi-typescript": "7.13.0",
45
+ "tsup": "8.5.1",
46
+ "typescript": "5.9.3",
47
+ "vitest": "4.1.11"
48
+ }
49
+ }