@aneuhold/core-ts-api-lib 3.0.34 → 3.0.35
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/CHANGELOG.md +8 -0
- package/lib/services/GCloudAPIService/GCloudAPIService.d.ts.map +1 -1
- package/lib/services/GCloudAPIService/GCloudAPIService.js +38 -7
- package/lib/services/GCloudAPIService/GCloudAPIService.js.map +1 -1
- package/lib/services/GCloudAPIService/GCloudAPIService.ts +60 -19
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 🔖 [3.0.35] (2026-04-21)
|
|
9
|
+
|
|
10
|
+
### 🏗️ Changed
|
|
11
|
+
|
|
12
|
+
- `GCloudAPIService.call` and `fetchAndDecode` are now typed with both `TInput` and `TOutput` type parameters for stricter input typing.
|
|
13
|
+
- `GCloudAPIService.decodeResponse` now validates the response shape with an `isAPIResponseShape` type guard instead of an unsafe cast. On failures it still returns an `APIResponse<T>` with `data` populated (as an empty object cast to `T`) so the response contract is preserved.
|
|
14
|
+
|
|
8
15
|
## 🔖 [3.0.34] (2026-04-17)
|
|
9
16
|
|
|
10
17
|
### 🏗️ Changed
|
|
@@ -397,6 +404,7 @@ No direct code changes; version bump for compatibility.
|
|
|
397
404
|
- Updated workflow permissions to allow repository write access
|
|
398
405
|
|
|
399
406
|
<!-- Link References -->
|
|
407
|
+
[3.0.35]: https://github.com/aneuhold/ts-libs/compare/core-ts-api-lib-v3.0.34...core-ts-api-lib-v3.0.35
|
|
400
408
|
[3.0.34]: https://github.com/aneuhold/ts-libs/compare/core-ts-api-lib-v3.0.33...core-ts-api-lib-v3.0.34
|
|
401
409
|
[3.0.33]: https://github.com/aneuhold/ts-libs/compare/core-ts-api-lib-v3.0.32...core-ts-api-lib-v3.0.33
|
|
402
410
|
[3.0.32]: https://github.com/aneuhold/ts-libs/compare/core-ts-api-lib-v3.0.31...core-ts-api-lib-v3.0.32
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GCloudAPIService.d.ts","sourceRoot":"","sources":["../../../src/services/GCloudAPIService/GCloudAPIService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"GCloudAPIService.d.ts","sourceRoot":"","sources":["../../../src/services/GCloudAPIService/GCloudAPIService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAK9D,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC5B,MAAM,+CAA+C,CAAC;AAEvD;;;GAGG;AACH,KAAK,yBAAyB,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC;AAE3F;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;;IACnC,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAmC;IAcrE;;;;OAIG;IACH,MAAM,CAAC,MAAM,IAAI,MAAM;IAIvB;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIhC;;;;OAIG;IACH,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1C;;;;;OAKG;IACH,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjD;;;;;;OAMG;IACH,MAAM,CAAC,oBAAoB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,GAAG,IAAI;IAI7E;;;;OAIG;WACU,gBAAgB,CAC3B,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAI/C;;;OAGG;WACU,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAU1D;;;;OAIG;WACU,gBAAgB,CAC3B,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAI/C;;;;OAIG;WACU,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;IAIxE;;;;OAIG;WACU,cAAc,CACzB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC;IAOpD;;;;;;;OAOG;mBACkB,IAAI;IAiBzB;;;;OAIG;mBACkB,gBAAgB;IAyBrC;;;;;OAKG;mBACkB,cAAc;IAuBnC;;;;OAIG;mBACkB,cAAc;CAsBpC"}
|
|
@@ -128,16 +128,15 @@ export default class GCloudAPIService {
|
|
|
128
128
|
if (!this.#refreshTokenString) {
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
|
-
const { decoded } = await this.fetchAndDecode('auth/refresh', {
|
|
132
|
-
refreshTokenString: this.#refreshTokenString
|
|
133
|
-
});
|
|
131
|
+
const { decoded } = await this.fetchAndDecode('auth/refresh', { refreshTokenString: this.#refreshTokenString });
|
|
134
132
|
if (!decoded.success) {
|
|
135
133
|
return false;
|
|
136
134
|
}
|
|
137
|
-
|
|
138
|
-
this.#
|
|
135
|
+
const { accessToken, refreshTokenString } = decoded.data;
|
|
136
|
+
this.#accessToken = accessToken;
|
|
137
|
+
this.#refreshTokenString = refreshTokenString;
|
|
139
138
|
if (this.#onTokensRefreshed) {
|
|
140
|
-
this.#onTokensRefreshed(
|
|
139
|
+
this.#onTokensRefreshed(accessToken, refreshTokenString);
|
|
141
140
|
}
|
|
142
141
|
return true;
|
|
143
142
|
}
|
|
@@ -172,15 +171,47 @@ export default class GCloudAPIService {
|
|
|
172
171
|
static async decodeResponse(response) {
|
|
173
172
|
try {
|
|
174
173
|
const text = await response.text();
|
|
175
|
-
|
|
174
|
+
const parsed = JSON.parse(text, DateService.dateReviver);
|
|
175
|
+
if (!isAPIResponseShape(parsed)) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
errors: ['Response did not match the expected APIResponse shape'],
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
180
|
+
data: {}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return parsed;
|
|
176
184
|
}
|
|
177
185
|
catch (error) {
|
|
178
186
|
return {
|
|
179
187
|
success: false,
|
|
180
188
|
errors: ['Failed to parse response', ErrorUtils.getErrorString(error)],
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
181
190
|
data: {}
|
|
182
191
|
};
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Type guard that validates the structural shape of an {@link APIResponse}.
|
|
197
|
+
* The generic `data` payload is trusted (not validated) since its shape is
|
|
198
|
+
* only known at the call site.
|
|
199
|
+
*
|
|
200
|
+
* @param value - The parsed JSON value to inspect.
|
|
201
|
+
*/
|
|
202
|
+
function isAPIResponseShape(value) {
|
|
203
|
+
if (typeof value !== 'object' || value === null) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (!('success' in value) || typeof value.success !== 'boolean') {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (!('errors' in value) || !Array.isArray(value.errors)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (!value.errors.every((err) => typeof err === 'string')) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return 'data' in value;
|
|
216
|
+
}
|
|
186
217
|
//# sourceMappingURL=GCloudAPIService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GCloudAPIService.js","sourceRoot":"","sources":["../../../src/services/GCloudAPIService/GCloudAPIService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"GCloudAPIService.js","sourceRoot":"","sources":["../../../src/services/GCloudAPIService/GCloudAPIService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { DateService, ErrorUtils } from '@aneuhold/core-ts-lib';
|
|
2
2
|
import type { AdminInput, AdminOutput } from '../../types/Admin.js';
|
|
3
3
|
import type { APIResponse } from '../../types/APIResponse.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AuthRefreshTokenInput,
|
|
6
|
+
AuthRefreshTokenOutput
|
|
7
|
+
} from '../../types/AuthRefreshToken.js';
|
|
5
8
|
import type {
|
|
6
9
|
AuthValidateUserInput,
|
|
7
10
|
AuthValidateUserOutput
|
|
@@ -95,7 +98,7 @@ export default class GCloudAPIService {
|
|
|
95
98
|
static async authValidateUser(
|
|
96
99
|
input: AuthValidateUserInput
|
|
97
100
|
): Promise<APIResponse<AuthValidateUserOutput>> {
|
|
98
|
-
return this.call<AuthValidateUserOutput>('auth/validateUser', input);
|
|
101
|
+
return this.call<AuthValidateUserInput, AuthValidateUserOutput>('auth/validateUser', input);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/**
|
|
@@ -106,7 +109,7 @@ export default class GCloudAPIService {
|
|
|
106
109
|
if (!this.#refreshTokenString) {
|
|
107
110
|
return { success: true, errors: [], data: undefined };
|
|
108
111
|
}
|
|
109
|
-
const { decoded } = await this.fetchAndDecode<undefined>('auth/logout', {
|
|
112
|
+
const { decoded } = await this.fetchAndDecode<AuthRefreshTokenInput, undefined>('auth/logout', {
|
|
110
113
|
refreshTokenString: this.#refreshTokenString
|
|
111
114
|
});
|
|
112
115
|
return decoded;
|
|
@@ -120,7 +123,7 @@ export default class GCloudAPIService {
|
|
|
120
123
|
static async projectDashboard(
|
|
121
124
|
input: ProjectDashboardInput
|
|
122
125
|
): Promise<APIResponse<ProjectDashboardOutput>> {
|
|
123
|
-
return this.call<ProjectDashboardOutput>('project/dashboard', input);
|
|
126
|
+
return this.call<ProjectDashboardInput, ProjectDashboardOutput>('project/dashboard', input);
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
/**
|
|
@@ -129,7 +132,7 @@ export default class GCloudAPIService {
|
|
|
129
132
|
* @param input - The input for the admin endpoint.
|
|
130
133
|
*/
|
|
131
134
|
static async admin(input: AdminInput): Promise<APIResponse<AdminOutput>> {
|
|
132
|
-
return this.call<AdminOutput>('admin', input);
|
|
135
|
+
return this.call<AdminInput, AdminOutput>('admin', input);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
/**
|
|
@@ -140,7 +143,10 @@ export default class GCloudAPIService {
|
|
|
140
143
|
static async projectWorkout(
|
|
141
144
|
input: ProjectWorkoutPrimaryInput
|
|
142
145
|
): Promise<APIResponse<ProjectWorkoutPrimaryOutput>> {
|
|
143
|
-
return this.call<ProjectWorkoutPrimaryOutput>(
|
|
146
|
+
return this.call<ProjectWorkoutPrimaryInput, ProjectWorkoutPrimaryOutput>(
|
|
147
|
+
'project/workout',
|
|
148
|
+
input
|
|
149
|
+
);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
/**
|
|
@@ -151,16 +157,16 @@ export default class GCloudAPIService {
|
|
|
151
157
|
* @param urlPath - The path to the endpoint.
|
|
152
158
|
* @param input - The input to the endpoint.
|
|
153
159
|
*/
|
|
154
|
-
private static async call<TOutput>(
|
|
160
|
+
private static async call<TInput, TOutput>(
|
|
155
161
|
urlPath: string,
|
|
156
|
-
input:
|
|
162
|
+
input: TInput
|
|
157
163
|
): Promise<APIResponse<TOutput>> {
|
|
158
|
-
const { response, decoded } = await this.fetchAndDecode<TOutput>(urlPath, input);
|
|
164
|
+
const { response, decoded } = await this.fetchAndDecode<TInput, TOutput>(urlPath, input);
|
|
159
165
|
|
|
160
166
|
if (response.status === 401 && this.#refreshTokenString) {
|
|
161
167
|
const refreshed = await this.tryRefreshTokens();
|
|
162
168
|
if (refreshed) {
|
|
163
|
-
const retry = await this.fetchAndDecode<TOutput>(urlPath, input);
|
|
169
|
+
const retry = await this.fetchAndDecode<TInput, TOutput>(urlPath, input);
|
|
164
170
|
return retry.decoded;
|
|
165
171
|
}
|
|
166
172
|
}
|
|
@@ -178,19 +184,21 @@ export default class GCloudAPIService {
|
|
|
178
184
|
return false;
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
const { decoded } = await this.fetchAndDecode<AuthRefreshTokenOutput>(
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
const { decoded } = await this.fetchAndDecode<AuthRefreshTokenInput, AuthRefreshTokenOutput>(
|
|
188
|
+
'auth/refresh',
|
|
189
|
+
{ refreshTokenString: this.#refreshTokenString }
|
|
190
|
+
);
|
|
184
191
|
|
|
185
192
|
if (!decoded.success) {
|
|
186
193
|
return false;
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
|
|
190
|
-
this.#
|
|
196
|
+
const { accessToken, refreshTokenString } = decoded.data;
|
|
197
|
+
this.#accessToken = accessToken;
|
|
198
|
+
this.#refreshTokenString = refreshTokenString;
|
|
191
199
|
|
|
192
200
|
if (this.#onTokensRefreshed) {
|
|
193
|
-
this.#onTokensRefreshed(
|
|
201
|
+
this.#onTokensRefreshed(accessToken, refreshTokenString);
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
return true;
|
|
@@ -202,9 +210,9 @@ export default class GCloudAPIService {
|
|
|
202
210
|
* @param urlPath - The path to the endpoint.
|
|
203
211
|
* @param input - The input to the endpoint.
|
|
204
212
|
*/
|
|
205
|
-
private static async fetchAndDecode<TOutput>(
|
|
213
|
+
private static async fetchAndDecode<TInput, TOutput>(
|
|
206
214
|
urlPath: string,
|
|
207
|
-
input:
|
|
215
|
+
input: TInput
|
|
208
216
|
): Promise<{ response: Response; decoded: APIResponse<TOutput> }> {
|
|
209
217
|
const headers = new Headers({
|
|
210
218
|
Connection: 'keep-alive',
|
|
@@ -233,13 +241,46 @@ export default class GCloudAPIService {
|
|
|
233
241
|
private static async decodeResponse<TOutput>(response: Response): Promise<APIResponse<TOutput>> {
|
|
234
242
|
try {
|
|
235
243
|
const text = await response.text();
|
|
236
|
-
|
|
244
|
+
const parsed: unknown = JSON.parse(text, DateService.dateReviver);
|
|
245
|
+
if (!isAPIResponseShape<TOutput>(parsed)) {
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
errors: ['Response did not match the expected APIResponse shape'],
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
250
|
+
data: {} as TOutput
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return parsed;
|
|
237
254
|
} catch (error) {
|
|
238
255
|
return {
|
|
239
256
|
success: false,
|
|
240
257
|
errors: ['Failed to parse response', ErrorUtils.getErrorString(error)],
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
241
259
|
data: {} as TOutput
|
|
242
260
|
};
|
|
243
261
|
}
|
|
244
262
|
}
|
|
245
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Type guard that validates the structural shape of an {@link APIResponse}.
|
|
267
|
+
* The generic `data` payload is trusted (not validated) since its shape is
|
|
268
|
+
* only known at the call site.
|
|
269
|
+
*
|
|
270
|
+
* @param value - The parsed JSON value to inspect.
|
|
271
|
+
*/
|
|
272
|
+
function isAPIResponseShape<TOutput>(value: unknown): value is APIResponse<TOutput> {
|
|
273
|
+
if (typeof value !== 'object' || value === null) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (!('success' in value) || typeof value.success !== 'boolean') {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
if (!('errors' in value) || !Array.isArray(value.errors)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (!value.errors.every((err) => typeof err === 'string')) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return 'data' in value;
|
|
286
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@aneuhold/core-ts-api-lib",
|
|
3
3
|
"author": "Anton G. Neuhold Jr.",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.35",
|
|
6
6
|
"description": "A library for interacting with the backend and defining the backend API for personal projects.",
|
|
7
7
|
"packageManager": "pnpm@10.25.0",
|
|
8
8
|
"type": "module",
|
|
@@ -72,20 +72,20 @@
|
|
|
72
72
|
"TypeScript"
|
|
73
73
|
],
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@aneuhold/core-ts-db-lib": "^5.0.
|
|
76
|
-
"@aneuhold/core-ts-lib": "^2.4.
|
|
75
|
+
"@aneuhold/core-ts-db-lib": "^5.0.7",
|
|
76
|
+
"@aneuhold/core-ts-lib": "^2.4.4",
|
|
77
77
|
"bson": "^7.0.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@aneuhold/local-npm-registry": "^0.2.
|
|
81
|
-
"@aneuhold/main-scripts": "^2.
|
|
80
|
+
"@aneuhold/local-npm-registry": "^0.2.31",
|
|
81
|
+
"@aneuhold/main-scripts": "^2.9.3",
|
|
82
82
|
"@types/node": "^25.0.2",
|
|
83
83
|
"eslint": "^9.39.2",
|
|
84
84
|
"jsr": "^0.13.5",
|
|
85
85
|
"prettier": "^3.7.4",
|
|
86
86
|
"rimraf": "^6.1.2",
|
|
87
87
|
"tsx": "^4.21.0",
|
|
88
|
-
"typescript": "^
|
|
88
|
+
"typescript": "^6.0.3",
|
|
89
89
|
"vitest": "^4.0.15"
|
|
90
90
|
}
|
|
91
91
|
}
|