@gobing-ai/ts-utils 0.3.1 → 0.3.2

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/access.d.ts CHANGED
@@ -8,5 +8,6 @@
8
8
  * rather than forking this module.
9
9
  */
10
10
  export declare function hasRole(profile: Record<string, unknown> | null | undefined, role: string): boolean;
11
+ /** Collect every role name from a profile — Zitadel IAM roles, `roles` arrays, and object-based role maps — into a deduplicated `string[]`. */
11
12
  export declare function getRoles(profile: Record<string, unknown> | null | undefined): string[];
12
13
  //# sourceMappingURL=access.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CA+BlG;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAkCtF"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CA+BlG;AAED,+IAA+I;AAC/I,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAkCtF"}
package/dist/access.js CHANGED
@@ -39,6 +39,7 @@ export function hasRole(profile, role) {
39
39
  }
40
40
  return false;
41
41
  }
42
+ /** Collect every role name from a profile — Zitadel IAM roles, `roles` arrays, and object-based role maps — into a deduplicated `string[]`. */
42
43
  export function getRoles(profile) {
43
44
  if (!profile)
44
45
  return [];
@@ -1,3 +1,4 @@
1
+ /** Standard HTTP/application error codes used in structured API responses. */
1
2
  export declare const API_ERROR_CODES: {
2
3
  readonly SUCCESS: 0;
3
4
  readonly NOT_FOUND: 404;
@@ -8,8 +9,11 @@ export declare const API_ERROR_CODES: {
8
9
  readonly CONFLICT: 409;
9
10
  readonly INTERNAL_ERROR: 500;
10
11
  };
12
+ /** Union of all values in {@link API_ERROR_CODES}. */
11
13
  export type ApiErrorCode = (typeof API_ERROR_CODES)[keyof typeof API_ERROR_CODES];
14
+ /** Sentiment tag carried in every API envelope. */
12
15
  export type ApiEnvelopeResult = 'success' | 'info' | 'warn' | 'error';
16
+ /** Envelope wrapping successful API responses. */
13
17
  export interface ApiSuccessEnvelope<T> {
14
18
  code: 0;
15
19
  message: string;
@@ -21,6 +25,7 @@ export interface ApiSuccessEnvelope<T> {
21
25
  offset?: number;
22
26
  };
23
27
  }
28
+ /** Envelope wrapping failed or problematic API responses. */
24
29
  export interface ApiErrorEnvelope {
25
30
  result: 'warn' | 'error';
26
31
  code: number;
@@ -28,21 +33,37 @@ export interface ApiErrorEnvelope {
28
33
  data: null;
29
34
  details?: unknown;
30
35
  }
36
+ /** Generic API response: either a success envelope or an error envelope. */
31
37
  export type ApiEnvelope<T> = ApiSuccessEnvelope<T> | ApiErrorEnvelope;
38
+ /** Build a generic 200-success API envelope. */
32
39
  export declare function successResponse<T>(data: T, message?: string): ApiSuccessEnvelope<T>;
40
+ /** Build a 200-success API envelope tagged as informational (`result: "info"`). */
33
41
  export declare function infoResponse<T>(data: T, message?: string): ApiSuccessEnvelope<T>;
42
+ /** Build a 200-success envelope for paginated list endpoints, tagging as `info` and attaching pagination `meta`. */
34
43
  export declare function paginatedResponse<T>(data: T[], meta: {
35
44
  total?: number;
36
45
  limit?: number;
37
46
  offset?: number;
38
47
  }, message?: string): ApiSuccessEnvelope<T[]>;
48
+ /**
49
+ * Build a structured API error envelope.
50
+ *
51
+ * Code ≥ 500 tags `result: "error"`; anything else tags `result: "warn"`.
52
+ */
39
53
  export declare function errorResponse(code: number, message: string, details?: unknown): ApiErrorEnvelope;
54
+ /** Convenience wrapper for a 404 "not found" API error. */
40
55
  export declare function notFoundResponse(message?: string, details?: unknown): ApiErrorEnvelope;
56
+ /** Convenience wrapper for a 422 "validation error" API error. */
41
57
  export declare function validationErrorResponse(details: unknown, message?: string): ApiErrorEnvelope;
58
+ /** Convenience wrapper for a 400 "bad request" API error. */
42
59
  export declare function badRequestResponse(message: string, details?: unknown): ApiErrorEnvelope;
60
+ /** Convenience wrapper for a 401 "unauthorized" API error. */
43
61
  export declare function unauthorizedResponse(message?: string, details?: unknown): ApiErrorEnvelope;
62
+ /** Convenience wrapper for a 403 "forbidden" API error. */
44
63
  export declare function forbiddenResponse(message?: string, details?: unknown): ApiErrorEnvelope;
64
+ /** Convenience wrapper for a 409 "conflict" API error. */
45
65
  export declare function conflictResponse(message?: string, details?: unknown): ApiErrorEnvelope;
66
+ /** Convenience wrapper for a 500 "internal server error" API error. */
46
67
  export declare function internalErrorResponse(message?: string, details?: unknown): ApiErrorEnvelope;
47
68
  /**
48
69
  * Bridge a thrown error to an API error envelope — the single mapping from the domain error layer
@@ -1 +1 @@
1
- {"version":3,"file":"api-response.d.ts","sourceRoot":"","sources":["../src/api-response.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe;;;;;;;;;CASlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAElF,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtE,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC;AAEtE,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,SAAY,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAOtF;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,SAAgC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAOvG;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAC/B,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACzD,OAAO,SAAgC,GACxC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAQzB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAahG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,SAAuB,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAEpG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,SAAsB,GAAG,gBAAgB,CAEzG;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAEvF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,SAA4B,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAE7G;AAED,wBAAgB,iBAAiB,CAAC,OAAO,SAAqB,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAEnG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,SAAsB,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAEnG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,SAA0B,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAE5G;AAcD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAOjF"}
1
+ {"version":3,"file":"api-response.d.ts","sourceRoot":"","sources":["../src/api-response.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { ErrorCode, isAppError } from './errors.js';
2
+ /** Standard HTTP/application error codes used in structured API responses. */
2
3
  export const API_ERROR_CODES = {
3
4
  SUCCESS: 0,
4
5
  NOT_FOUND: 404,
@@ -9,6 +10,7 @@ export const API_ERROR_CODES = {
9
10
  CONFLICT: 409,
10
11
  INTERNAL_ERROR: 500,
11
12
  };
13
+ /** Build a generic 200-success API envelope. */
12
14
  export function successResponse(data, message = 'Success') {
13
15
  return {
14
16
  code: API_ERROR_CODES.SUCCESS,
@@ -17,6 +19,7 @@ export function successResponse(data, message = 'Success') {
17
19
  data,
18
20
  };
19
21
  }
22
+ /** Build a 200-success API envelope tagged as informational (`result: "info"`). */
20
23
  export function infoResponse(data, message = 'Data retrieved successfully') {
21
24
  return {
22
25
  code: API_ERROR_CODES.SUCCESS,
@@ -25,6 +28,7 @@ export function infoResponse(data, message = 'Data retrieved successfully') {
25
28
  data,
26
29
  };
27
30
  }
31
+ /** Build a 200-success envelope for paginated list endpoints, tagging as `info` and attaching pagination `meta`. */
28
32
  export function paginatedResponse(data, meta, message = 'Data retrieved successfully') {
29
33
  return {
30
34
  code: API_ERROR_CODES.SUCCESS,
@@ -34,6 +38,11 @@ export function paginatedResponse(data, meta, message = 'Data retrieved successf
34
38
  meta,
35
39
  };
36
40
  }
41
+ /**
42
+ * Build a structured API error envelope.
43
+ *
44
+ * Code ≥ 500 tags `result: "error"`; anything else tags `result: "warn"`.
45
+ */
37
46
  export function errorResponse(code, message, details) {
38
47
  const response = {
39
48
  code,
@@ -46,24 +55,31 @@ export function errorResponse(code, message, details) {
46
55
  }
47
56
  return response;
48
57
  }
58
+ /** Convenience wrapper for a 404 "not found" API error. */
49
59
  export function notFoundResponse(message = 'Resource not found', details) {
50
60
  return errorResponse(API_ERROR_CODES.NOT_FOUND, message, details);
51
61
  }
62
+ /** Convenience wrapper for a 422 "validation error" API error. */
52
63
  export function validationErrorResponse(details, message = 'Validation failed') {
53
64
  return errorResponse(API_ERROR_CODES.VALIDATION_ERROR, message, details);
54
65
  }
66
+ /** Convenience wrapper for a 400 "bad request" API error. */
55
67
  export function badRequestResponse(message, details) {
56
68
  return errorResponse(API_ERROR_CODES.BAD_REQUEST, message, details);
57
69
  }
70
+ /** Convenience wrapper for a 401 "unauthorized" API error. */
58
71
  export function unauthorizedResponse(message = 'Authentication required', details) {
59
72
  return errorResponse(API_ERROR_CODES.UNAUTHORIZED, message, details);
60
73
  }
74
+ /** Convenience wrapper for a 403 "forbidden" API error. */
61
75
  export function forbiddenResponse(message = 'Access forbidden', details) {
62
76
  return errorResponse(API_ERROR_CODES.FORBIDDEN, message, details);
63
77
  }
78
+ /** Convenience wrapper for a 409 "conflict" API error. */
64
79
  export function conflictResponse(message = 'Resource conflict', details) {
65
80
  return errorResponse(API_ERROR_CODES.CONFLICT, message, details);
66
81
  }
82
+ /** Convenience wrapper for a 500 "internal server error" API error. */
67
83
  export function internalErrorResponse(message = 'Internal server error', details) {
68
84
  return errorResponse(API_ERROR_CODES.INTERNAL_ERROR, message, details);
69
85
  }
package/dist/const.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /** Log category identifier for application-level messages. */
1
2
  export declare const LOG_CATEGORY_APP = "app";
3
+ /** Log category identifier for CLI-level messages. */
2
4
  export declare const LOG_CATEGORY_CLI = "cli";
3
5
  //# sourceMappingURL=const.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,gBAAgB,QAAQ,CAAC"}
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,QAAQ,CAAC"}
package/dist/const.js CHANGED
@@ -1,2 +1,4 @@
1
+ /** Log category identifier for application-level messages. */
1
2
  export const LOG_CATEGORY_APP = 'app';
3
+ /** Log category identifier for CLI-level messages. */
2
4
  export const LOG_CATEGORY_CLI = 'cli';
package/dist/cursor.d.ts CHANGED
@@ -1,14 +1,22 @@
1
+ /** Data embedded in a cursor-based pagination token. */
1
2
  export interface CursorData {
2
3
  id: string;
3
4
  createdAt?: number;
4
5
  offset?: number;
5
6
  }
7
+ /** Build a {@link CursorData} record from raw fields. */
6
8
  export declare function createCursor(id: string, createdAt?: Date | number, offset?: number): CursorData;
9
+ /** Parse a raw cursor payload (JSON string or parsed object) into validated {@link CursorData}. */
7
10
  export declare function parseCursor(data: string | Record<string, unknown>): CursorData;
11
+ /** Encode {@link CursorData} into an opaque base64url cursor string. */
8
12
  export declare function encodeCursor(cursor: CursorData): string;
13
+ /** Decode a base64url cursor string back to its JSON representation. */
9
14
  export declare function decodeCursor(encoded: string): string;
15
+ /** One-shot: create a cursor from item fields and encode it immediately. */
10
16
  export declare function encodeCursorFromItem(id: string, createdAt?: Date | number, offset?: number): string;
17
+ /** Decode a base64url cursor string and parse it into validated {@link CursorData}. Enforces a size cap to reject hostile input. */
11
18
  export declare function decodeAndParseCursor(encoded: string): CursorData;
19
+ /** Generate pagination metadata (`nextCursor`, `hasMore`, `limit`) for a list result. Uses the last item's `id` and `createdAt` as the cursor anchor. */
12
20
  export declare function buildCursorMeta<T extends {
13
21
  id: string;
14
22
  createdAt?: number | Date;
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAY/F;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAmB9E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEvD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAKhE;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC/E,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACjB;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAc1D"}
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../src/cursor.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAY/F;AAED,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAmB9E;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEvD;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnG;AAED,oIAAoI;AACpI,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAKhE;AAED,yJAAyJ;AACzJ,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC/E,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACjB;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAc1D"}
package/dist/cursor.js CHANGED
@@ -4,6 +4,7 @@ import { toMs } from './date.js';
4
4
  * input is hostile (cursors are client-supplied pagination tokens) and is rejected before decode.
5
5
  */
6
6
  const MAX_ENCODED_CURSOR_LENGTH = 1024;
7
+ /** Build a {@link CursorData} record from raw fields. */
7
8
  export function createCursor(id, createdAt, offset) {
8
9
  const cursor = { id };
9
10
  if (createdAt !== undefined) {
@@ -17,6 +18,7 @@ export function createCursor(id, createdAt, offset) {
17
18
  }
18
19
  return cursor;
19
20
  }
21
+ /** Parse a raw cursor payload (JSON string or parsed object) into validated {@link CursorData}. */
20
22
  export function parseCursor(data) {
21
23
  const parsed = typeof data === 'string' ? JSON.parse(data) : data;
22
24
  if (!parsed || typeof parsed !== 'object') {
@@ -34,21 +36,26 @@ export function parseCursor(data) {
34
36
  }
35
37
  return result;
36
38
  }
39
+ /** Encode {@link CursorData} into an opaque base64url cursor string. */
37
40
  export function encodeCursor(cursor) {
38
41
  return base64UrlEncode(JSON.stringify(cursor));
39
42
  }
43
+ /** Decode a base64url cursor string back to its JSON representation. */
40
44
  export function decodeCursor(encoded) {
41
45
  return base64UrlDecode(encoded);
42
46
  }
47
+ /** One-shot: create a cursor from item fields and encode it immediately. */
43
48
  export function encodeCursorFromItem(id, createdAt, offset) {
44
49
  return encodeCursor(createCursor(id, createdAt, offset));
45
50
  }
51
+ /** Decode a base64url cursor string and parse it into validated {@link CursorData}. Enforces a size cap to reject hostile input. */
46
52
  export function decodeAndParseCursor(encoded) {
47
53
  if (encoded.length > MAX_ENCODED_CURSOR_LENGTH) {
48
54
  throw new Error('Invalid cursor: exceeds maximum length');
49
55
  }
50
56
  return parseCursor(decodeCursor(encoded));
51
57
  }
58
+ /** Generate pagination metadata (`nextCursor`, `hasMore`, `limit`) for a list result. Uses the last item's `id` and `createdAt` as the cursor anchor. */
52
59
  export function buildCursorMeta(items, limit, hasMore) {
53
60
  const meta = {
54
61
  hasMore,
package/dist/date.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ /** Current time in milliseconds since the Unix epoch. */
1
2
  export declare function nowMs(): number;
3
+ /** Normalize a date/time input to milliseconds since the Unix epoch. Returns `null` for unparseable or invalid values. */
2
4
  export declare function toMs(input: Date | number | string | null | undefined): number | null;
5
+ /** Convert a millisecond timestamp to a `Date`. Returns `null` for `null`, `undefined`, or `NaN`. */
3
6
  export declare function fromMs(ms: number | null | undefined): Date | null;
4
7
  //# sourceMappingURL=date.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../src/date.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CASpF;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAGjE"}
1
+ {"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../src/date.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,0HAA0H;AAC1H,wBAAgB,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CASpF;AAED,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAGjE"}
package/dist/date.js CHANGED
@@ -1,6 +1,8 @@
1
+ /** Current time in milliseconds since the Unix epoch. */
1
2
  export function nowMs() {
2
3
  return Date.now();
3
4
  }
5
+ /** Normalize a date/time input to milliseconds since the Unix epoch. Returns `null` for unparseable or invalid values. */
4
6
  export function toMs(input) {
5
7
  if (input === null || input === undefined)
6
8
  return null;
@@ -13,6 +15,7 @@ export function toMs(input) {
13
15
  // Reject NaN/±Infinity rather than passing them through as a "valid" timestamp.
14
16
  return Number.isFinite(input) ? Math.floor(input) : null;
15
17
  }
18
+ /** Convert a millisecond timestamp to a `Date`. Returns `null` for `null`, `undefined`, or `NaN`. */
16
19
  export function fromMs(ms) {
17
20
  if (ms === null || ms === undefined || Number.isNaN(ms))
18
21
  return null;
package/dist/errors.d.ts CHANGED
@@ -1,26 +1,34 @@
1
+ /** Canonical domain-error codes carried by every {@link AppError}. */
1
2
  export declare const ErrorCode: {
2
3
  readonly NotFound: "NOT_FOUND";
3
4
  readonly Validation: "VALIDATION";
4
5
  readonly Conflict: "CONFLICT";
5
6
  readonly Internal: "INTERNAL";
6
7
  };
8
+ /** Union of all values in the {@link ErrorCode} const object. */
7
9
  export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
10
+ /** Base application error: holds a machine-readable {@link ErrorCode} `code`. */
8
11
  export declare class AppError extends Error {
9
12
  readonly code: ErrorCode;
10
13
  constructor(code: ErrorCode, message: string);
11
14
  }
15
+ /** Specialized {@link AppError} for "not found" scenarios. Carries `ErrorCode.NotFound`. */
12
16
  export declare class NotFoundError extends AppError {
13
17
  constructor(message: string);
14
18
  }
19
+ /** Specialized {@link AppError} for validation failures. Carries `ErrorCode.Validation`. */
15
20
  export declare class ValidationError extends AppError {
16
21
  constructor(message: string);
17
22
  }
23
+ /** Specialized {@link AppError} for resource conflicts. Carries `ErrorCode.Conflict`. */
18
24
  export declare class ConflictError extends AppError {
19
25
  constructor(message: string);
20
26
  }
27
+ /** Specialized {@link AppError} for unexpected internal failures. Carries `ErrorCode.Internal` and an optional root `cause`. */
21
28
  export declare class InternalError extends AppError {
22
29
  readonly cause?: unknown | undefined;
23
30
  constructor(message: string, cause?: unknown | undefined);
24
31
  }
32
+ /** Type guard: narrows an unknown error value to {@link AppError}. */
25
33
  export declare function isAppError(error: unknown): error is AppError;
26
34
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,qBAAa,QAAS,SAAQ,KAAK;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;gBAEb,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;CAK/C;AAED,qBAAa,aAAc,SAAQ,QAAQ;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED,qBAAa,eAAgB,SAAQ,QAAQ;gBAC7B,OAAO,EAAE,MAAM;CAI9B;AAED,qBAAa,aAAc,SAAQ,QAAQ;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED,qBAAa,aAAc,SAAQ,QAAQ;aAGjB,KAAK,CAAC,EAAE,OAAO;gBADjC,OAAO,EAAE,MAAM,EACG,KAAK,CAAC,EAAE,OAAO,YAAA;CAKxC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX,iEAAiE;AACjE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,iFAAiF;AACjF,qBAAa,QAAS,SAAQ,KAAK;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;gBAEb,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;CAK/C;AAED,4FAA4F;AAC5F,qBAAa,aAAc,SAAQ,QAAQ;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED,4FAA4F;AAC5F,qBAAa,eAAgB,SAAQ,QAAQ;gBAC7B,OAAO,EAAE,MAAM;CAI9B;AAED,yFAAyF;AACzF,qBAAa,aAAc,SAAQ,QAAQ;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED,gIAAgI;AAChI,qBAAa,aAAc,SAAQ,QAAQ;aAGjB,KAAK,CAAC,EAAE,OAAO;gBADjC,OAAO,EAAE,MAAM,EACG,KAAK,CAAC,EAAE,OAAO,YAAA;CAKxC;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
package/dist/errors.js CHANGED
@@ -1,9 +1,11 @@
1
+ /** Canonical domain-error codes carried by every {@link AppError}. */
1
2
  export const ErrorCode = {
2
3
  NotFound: 'NOT_FOUND',
3
4
  Validation: 'VALIDATION',
4
5
  Conflict: 'CONFLICT',
5
6
  Internal: 'INTERNAL',
6
7
  };
8
+ /** Base application error: holds a machine-readable {@link ErrorCode} `code`. */
7
9
  export class AppError extends Error {
8
10
  code;
9
11
  constructor(code, message) {
@@ -12,24 +14,28 @@ export class AppError extends Error {
12
14
  this.code = code;
13
15
  }
14
16
  }
17
+ /** Specialized {@link AppError} for "not found" scenarios. Carries `ErrorCode.NotFound`. */
15
18
  export class NotFoundError extends AppError {
16
19
  constructor(message) {
17
20
  super(ErrorCode.NotFound, message);
18
21
  this.name = 'NotFoundError';
19
22
  }
20
23
  }
24
+ /** Specialized {@link AppError} for validation failures. Carries `ErrorCode.Validation`. */
21
25
  export class ValidationError extends AppError {
22
26
  constructor(message) {
23
27
  super(ErrorCode.Validation, message);
24
28
  this.name = 'ValidationError';
25
29
  }
26
30
  }
31
+ /** Specialized {@link AppError} for resource conflicts. Carries `ErrorCode.Conflict`. */
27
32
  export class ConflictError extends AppError {
28
33
  constructor(message) {
29
34
  super(ErrorCode.Conflict, message);
30
35
  this.name = 'ConflictError';
31
36
  }
32
37
  }
38
+ /** Specialized {@link AppError} for unexpected internal failures. Carries `ErrorCode.Internal` and an optional root `cause`. */
33
39
  export class InternalError extends AppError {
34
40
  cause;
35
41
  constructor(message, cause) {
@@ -38,6 +44,7 @@ export class InternalError extends AppError {
38
44
  this.name = 'InternalError';
39
45
  }
40
46
  }
47
+ /** Type guard: narrows an unknown error value to {@link AppError}. */
41
48
  export function isAppError(error) {
42
49
  return error instanceof AppError;
43
50
  }
package/dist/origin.d.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * matches nothing unless it equals the origin verbatim). Keep CORS patterns to a single wildcard.
6
6
  */
7
7
  export declare function matchOriginPattern(origin: string, pattern: string): boolean;
8
+ /** Check whether a request origin matches any entry in an allowlist. Delegates to {@link matchOriginPattern} per entry. */
8
9
  export declare function isAllowedOrigin(origin: string | undefined | null, allowedOrigins: string[]): boolean;
10
+ /** Return the request origin if it is in the allowlist; otherwise return a safe `fallback` origin. */
9
11
  export declare function getValidatedOrigin(origin: string | undefined | null, allowedOrigins: string[], fallback: string): string;
10
12
  //# sourceMappingURL=origin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAgB3E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAKpG;AAED,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACjC,cAAc,EAAE,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,GACjB,MAAM,CAKR"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAgB3E;AAED,2HAA2H;AAC3H,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAKpG;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACjC,cAAc,EAAE,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,GACjB,MAAM,CAKR"}
package/dist/origin.js CHANGED
@@ -22,6 +22,7 @@ export function matchOriginPattern(origin, pattern) {
22
22
  }
23
23
  return false;
24
24
  }
25
+ /** Check whether a request origin matches any entry in an allowlist. Delegates to {@link matchOriginPattern} per entry. */
25
26
  export function isAllowedOrigin(origin, allowedOrigins) {
26
27
  if (!origin)
27
28
  return false;
@@ -29,6 +30,7 @@ export function isAllowedOrigin(origin, allowedOrigins) {
29
30
  return false;
30
31
  return allowedOrigins.some((pattern) => matchOriginPattern(origin, pattern));
31
32
  }
33
+ /** Return the request origin if it is in the allowlist; otherwise return a safe `fallback` origin. */
32
34
  export function getValidatedOrigin(origin, allowedOrigins, fallback) {
33
35
  if (origin && isAllowedOrigin(origin, allowedOrigins)) {
34
36
  return origin;
package/dist/output.d.ts CHANGED
@@ -1,16 +1,26 @@
1
+ /** Minimal write sink: anything that accepts a string chunk. */
1
2
  export interface WriteTarget {
2
3
  write(chunk: string): unknown;
3
4
  }
5
+ /** Write a line to a target, defaulting to stdout. */
4
6
  export declare function echo(message: string, target?: WriteTarget): void;
7
+ /** Write a line to a target, defaulting to stderr. */
5
8
  export declare function echoError(message: string, target?: WriteTarget): void;
9
+ /**
10
+ * Override the default stdout/stderr targets.
11
+ *
12
+ * Returns a rollback function that restores the previous targets.
13
+ */
6
14
  export declare function setDefaultOutputTargets(opts: {
7
15
  stdout?: WriteTarget;
8
16
  stderr?: WriteTarget;
9
17
  }): () => void;
18
+ /** In-memory `WriteTarget` that records all chunks for later retrieval. */
10
19
  export interface BufferTarget extends WriteTarget {
11
20
  readonly chunks: string[];
12
21
  text(): string;
13
22
  clear(): void;
14
23
  }
24
+ /** Create an in-memory {@link BufferTarget} for capturing output during tests or tooling. */
15
25
  export declare function createBufferTarget(): BufferTarget;
16
26
  //# sourceMappingURL=output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IACxB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAoBD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAA4D,GAAG,IAAI,CAEhH;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAA4D,GAAG,IAAI,CAErH;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,MAAM,IAAI,CASxG;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,IAAI,MAAM,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAejD"}
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,WAAW,WAAW;IACxB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAoBD,sDAAsD;AACtD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAA4D,GAAG,IAAI,CAEhH;AAED,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAA4D,GAAG,IAAI,CAErH;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,MAAM,IAAI,CASxG;AAED,2EAA2E;AAC3E,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,IAAI,MAAM,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,6FAA6F;AAC7F,wBAAgB,kBAAkB,IAAI,YAAY,CAejD"}
package/dist/output.js CHANGED
@@ -13,12 +13,19 @@ function processStream(name) {
13
13
  function writeLine(message, target) {
14
14
  target.write(`${message}\n`);
15
15
  }
16
+ /** Write a line to a target, defaulting to stdout. */
16
17
  export function echo(message, target = defaultStdoutTarget ?? processStream('stdout')) {
17
18
  writeLine(message, target);
18
19
  }
20
+ /** Write a line to a target, defaulting to stderr. */
19
21
  export function echoError(message, target = defaultStderrTarget ?? processStream('stderr')) {
20
22
  writeLine(message, target);
21
23
  }
24
+ /**
25
+ * Override the default stdout/stderr targets.
26
+ *
27
+ * Returns a rollback function that restores the previous targets.
28
+ */
22
29
  export function setDefaultOutputTargets(opts) {
23
30
  const prevStdout = defaultStdoutTarget;
24
31
  const prevStderr = defaultStderrTarget;
@@ -31,6 +38,7 @@ export function setDefaultOutputTargets(opts) {
31
38
  defaultStderrTarget = prevStderr;
32
39
  };
33
40
  }
41
+ /** Create an in-memory {@link BufferTarget} for capturing output during tests or tooling. */
34
42
  export function createBufferTarget() {
35
43
  const chunks = [];
36
44
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-utils",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Zero-dependency TypeScript utilities for dates, cursors, errors, output, origins, roles, and API responses.",
5
5
  "keywords": [
6
6
  "typescript",
package/src/access.ts CHANGED
@@ -40,6 +40,7 @@ export function hasRole(profile: Record<string, unknown> | null | undefined, rol
40
40
  return false;
41
41
  }
42
42
 
43
+ /** Collect every role name from a profile — Zitadel IAM roles, `roles` arrays, and object-based role maps — into a deduplicated `string[]`. */
43
44
  export function getRoles(profile: Record<string, unknown> | null | undefined): string[] {
44
45
  if (!profile) return [];
45
46
 
@@ -1,5 +1,6 @@
1
1
  import { type AppError, ErrorCode, isAppError } from './errors';
2
2
 
3
+ /** Standard HTTP/application error codes used in structured API responses. */
3
4
  export const API_ERROR_CODES = {
4
5
  SUCCESS: 0,
5
6
  NOT_FOUND: 404,
@@ -11,10 +12,13 @@ export const API_ERROR_CODES = {
11
12
  INTERNAL_ERROR: 500,
12
13
  } as const;
13
14
 
15
+ /** Union of all values in {@link API_ERROR_CODES}. */
14
16
  export type ApiErrorCode = (typeof API_ERROR_CODES)[keyof typeof API_ERROR_CODES];
15
17
 
18
+ /** Sentiment tag carried in every API envelope. */
16
19
  export type ApiEnvelopeResult = 'success' | 'info' | 'warn' | 'error';
17
20
 
21
+ /** Envelope wrapping successful API responses. */
18
22
  export interface ApiSuccessEnvelope<T> {
19
23
  code: 0;
20
24
  message: string;
@@ -23,6 +27,7 @@ export interface ApiSuccessEnvelope<T> {
23
27
  meta?: { total?: number; limit?: number; offset?: number };
24
28
  }
25
29
 
30
+ /** Envelope wrapping failed or problematic API responses. */
26
31
  export interface ApiErrorEnvelope {
27
32
  result: 'warn' | 'error';
28
33
  code: number;
@@ -31,8 +36,10 @@ export interface ApiErrorEnvelope {
31
36
  details?: unknown;
32
37
  }
33
38
 
39
+ /** Generic API response: either a success envelope or an error envelope. */
34
40
  export type ApiEnvelope<T> = ApiSuccessEnvelope<T> | ApiErrorEnvelope;
35
41
 
42
+ /** Build a generic 200-success API envelope. */
36
43
  export function successResponse<T>(data: T, message = 'Success'): ApiSuccessEnvelope<T> {
37
44
  return {
38
45
  code: API_ERROR_CODES.SUCCESS,
@@ -42,6 +49,7 @@ export function successResponse<T>(data: T, message = 'Success'): ApiSuccessEnve
42
49
  };
43
50
  }
44
51
 
52
+ /** Build a 200-success API envelope tagged as informational (`result: "info"`). */
45
53
  export function infoResponse<T>(data: T, message = 'Data retrieved successfully'): ApiSuccessEnvelope<T> {
46
54
  return {
47
55
  code: API_ERROR_CODES.SUCCESS,
@@ -51,6 +59,7 @@ export function infoResponse<T>(data: T, message = 'Data retrieved successfully'
51
59
  };
52
60
  }
53
61
 
62
+ /** Build a 200-success envelope for paginated list endpoints, tagging as `info` and attaching pagination `meta`. */
54
63
  export function paginatedResponse<T>(
55
64
  data: T[],
56
65
  meta: { total?: number; limit?: number; offset?: number },
@@ -65,6 +74,11 @@ export function paginatedResponse<T>(
65
74
  };
66
75
  }
67
76
 
77
+ /**
78
+ * Build a structured API error envelope.
79
+ *
80
+ * Code ≥ 500 tags `result: "error"`; anything else tags `result: "warn"`.
81
+ */
68
82
  export function errorResponse(code: number, message: string, details?: unknown): ApiErrorEnvelope {
69
83
  const response: ApiErrorEnvelope = {
70
84
  code,
@@ -80,30 +94,37 @@ export function errorResponse(code: number, message: string, details?: unknown):
80
94
  return response;
81
95
  }
82
96
 
97
+ /** Convenience wrapper for a 404 "not found" API error. */
83
98
  export function notFoundResponse(message = 'Resource not found', details?: unknown): ApiErrorEnvelope {
84
99
  return errorResponse(API_ERROR_CODES.NOT_FOUND, message, details);
85
100
  }
86
101
 
102
+ /** Convenience wrapper for a 422 "validation error" API error. */
87
103
  export function validationErrorResponse(details: unknown, message = 'Validation failed'): ApiErrorEnvelope {
88
104
  return errorResponse(API_ERROR_CODES.VALIDATION_ERROR, message, details);
89
105
  }
90
106
 
107
+ /** Convenience wrapper for a 400 "bad request" API error. */
91
108
  export function badRequestResponse(message: string, details?: unknown): ApiErrorEnvelope {
92
109
  return errorResponse(API_ERROR_CODES.BAD_REQUEST, message, details);
93
110
  }
94
111
 
112
+ /** Convenience wrapper for a 401 "unauthorized" API error. */
95
113
  export function unauthorizedResponse(message = 'Authentication required', details?: unknown): ApiErrorEnvelope {
96
114
  return errorResponse(API_ERROR_CODES.UNAUTHORIZED, message, details);
97
115
  }
98
116
 
117
+ /** Convenience wrapper for a 403 "forbidden" API error. */
99
118
  export function forbiddenResponse(message = 'Access forbidden', details?: unknown): ApiErrorEnvelope {
100
119
  return errorResponse(API_ERROR_CODES.FORBIDDEN, message, details);
101
120
  }
102
121
 
122
+ /** Convenience wrapper for a 409 "conflict" API error. */
103
123
  export function conflictResponse(message = 'Resource conflict', details?: unknown): ApiErrorEnvelope {
104
124
  return errorResponse(API_ERROR_CODES.CONFLICT, message, details);
105
125
  }
106
126
 
127
+ /** Convenience wrapper for a 500 "internal server error" API error. */
107
128
  export function internalErrorResponse(message = 'Internal server error', details?: unknown): ApiErrorEnvelope {
108
129
  return errorResponse(API_ERROR_CODES.INTERNAL_ERROR, message, details);
109
130
  }
package/src/const.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /** Log category identifier for application-level messages. */
1
2
  export const LOG_CATEGORY_APP = 'app';
2
3
 
4
+ /** Log category identifier for CLI-level messages. */
3
5
  export const LOG_CATEGORY_CLI = 'cli';
package/src/cursor.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { toMs } from './date';
2
2
 
3
+ /** Data embedded in a cursor-based pagination token. */
3
4
  export interface CursorData {
4
5
  id: string;
5
6
  createdAt?: number;
@@ -12,6 +13,7 @@ export interface CursorData {
12
13
  */
13
14
  const MAX_ENCODED_CURSOR_LENGTH = 1024;
14
15
 
16
+ /** Build a {@link CursorData} record from raw fields. */
15
17
  export function createCursor(id: string, createdAt?: Date | number, offset?: number): CursorData {
16
18
  const cursor: CursorData = { id };
17
19
  if (createdAt !== undefined) {
@@ -26,6 +28,7 @@ export function createCursor(id: string, createdAt?: Date | number, offset?: num
26
28
  return cursor;
27
29
  }
28
30
 
31
+ /** Parse a raw cursor payload (JSON string or parsed object) into validated {@link CursorData}. */
29
32
  export function parseCursor(data: string | Record<string, unknown>): CursorData {
30
33
  const parsed = typeof data === 'string' ? (JSON.parse(data) as Record<string, unknown>) : data;
31
34
 
@@ -47,18 +50,22 @@ export function parseCursor(data: string | Record<string, unknown>): CursorData
47
50
  return result;
48
51
  }
49
52
 
53
+ /** Encode {@link CursorData} into an opaque base64url cursor string. */
50
54
  export function encodeCursor(cursor: CursorData): string {
51
55
  return base64UrlEncode(JSON.stringify(cursor));
52
56
  }
53
57
 
58
+ /** Decode a base64url cursor string back to its JSON representation. */
54
59
  export function decodeCursor(encoded: string): string {
55
60
  return base64UrlDecode(encoded);
56
61
  }
57
62
 
63
+ /** One-shot: create a cursor from item fields and encode it immediately. */
58
64
  export function encodeCursorFromItem(id: string, createdAt?: Date | number, offset?: number): string {
59
65
  return encodeCursor(createCursor(id, createdAt, offset));
60
66
  }
61
67
 
68
+ /** Decode a base64url cursor string and parse it into validated {@link CursorData}. Enforces a size cap to reject hostile input. */
62
69
  export function decodeAndParseCursor(encoded: string): CursorData {
63
70
  if (encoded.length > MAX_ENCODED_CURSOR_LENGTH) {
64
71
  throw new Error('Invalid cursor: exceeds maximum length');
@@ -66,6 +73,7 @@ export function decodeAndParseCursor(encoded: string): CursorData {
66
73
  return parseCursor(decodeCursor(encoded));
67
74
  }
68
75
 
76
+ /** Generate pagination metadata (`nextCursor`, `hasMore`, `limit`) for a list result. Uses the last item's `id` and `createdAt` as the cursor anchor. */
69
77
  export function buildCursorMeta<T extends { id: string; createdAt?: number | Date }>(
70
78
  items: T[],
71
79
  limit: number,
package/src/date.ts CHANGED
@@ -1,7 +1,9 @@
1
+ /** Current time in milliseconds since the Unix epoch. */
1
2
  export function nowMs(): number {
2
3
  return Date.now();
3
4
  }
4
5
 
6
+ /** Normalize a date/time input to milliseconds since the Unix epoch. Returns `null` for unparseable or invalid values. */
5
7
  export function toMs(input: Date | number | string | null | undefined): number | null {
6
8
  if (input === null || input === undefined) return null;
7
9
  if (input instanceof Date) return input.getTime();
@@ -13,6 +15,7 @@ export function toMs(input: Date | number | string | null | undefined): number |
13
15
  return Number.isFinite(input) ? Math.floor(input) : null;
14
16
  }
15
17
 
18
+ /** Convert a millisecond timestamp to a `Date`. Returns `null` for `null`, `undefined`, or `NaN`. */
16
19
  export function fromMs(ms: number | null | undefined): Date | null {
17
20
  if (ms === null || ms === undefined || Number.isNaN(ms)) return null;
18
21
  return new Date(ms);
package/src/errors.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** Canonical domain-error codes carried by every {@link AppError}. */
1
2
  export const ErrorCode = {
2
3
  NotFound: 'NOT_FOUND',
3
4
  Validation: 'VALIDATION',
@@ -5,8 +6,10 @@ export const ErrorCode = {
5
6
  Internal: 'INTERNAL',
6
7
  } as const;
7
8
 
9
+ /** Union of all values in the {@link ErrorCode} const object. */
8
10
  export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
9
11
 
12
+ /** Base application error: holds a machine-readable {@link ErrorCode} `code`. */
10
13
  export class AppError extends Error {
11
14
  readonly code: ErrorCode;
12
15
 
@@ -17,6 +20,7 @@ export class AppError extends Error {
17
20
  }
18
21
  }
19
22
 
23
+ /** Specialized {@link AppError} for "not found" scenarios. Carries `ErrorCode.NotFound`. */
20
24
  export class NotFoundError extends AppError {
21
25
  constructor(message: string) {
22
26
  super(ErrorCode.NotFound, message);
@@ -24,6 +28,7 @@ export class NotFoundError extends AppError {
24
28
  }
25
29
  }
26
30
 
31
+ /** Specialized {@link AppError} for validation failures. Carries `ErrorCode.Validation`. */
27
32
  export class ValidationError extends AppError {
28
33
  constructor(message: string) {
29
34
  super(ErrorCode.Validation, message);
@@ -31,6 +36,7 @@ export class ValidationError extends AppError {
31
36
  }
32
37
  }
33
38
 
39
+ /** Specialized {@link AppError} for resource conflicts. Carries `ErrorCode.Conflict`. */
34
40
  export class ConflictError extends AppError {
35
41
  constructor(message: string) {
36
42
  super(ErrorCode.Conflict, message);
@@ -38,6 +44,7 @@ export class ConflictError extends AppError {
38
44
  }
39
45
  }
40
46
 
47
+ /** Specialized {@link AppError} for unexpected internal failures. Carries `ErrorCode.Internal` and an optional root `cause`. */
41
48
  export class InternalError extends AppError {
42
49
  constructor(
43
50
  message: string,
@@ -48,6 +55,7 @@ export class InternalError extends AppError {
48
55
  }
49
56
  }
50
57
 
58
+ /** Type guard: narrows an unknown error value to {@link AppError}. */
51
59
  export function isAppError(error: unknown): error is AppError {
52
60
  return error instanceof AppError;
53
61
  }
package/src/origin.ts CHANGED
@@ -22,6 +22,7 @@ export function matchOriginPattern(origin: string, pattern: string): boolean {
22
22
  return false;
23
23
  }
24
24
 
25
+ /** Check whether a request origin matches any entry in an allowlist. Delegates to {@link matchOriginPattern} per entry. */
25
26
  export function isAllowedOrigin(origin: string | undefined | null, allowedOrigins: string[]): boolean {
26
27
  if (!origin) return false;
27
28
  if (!allowedOrigins || allowedOrigins.length === 0) return false;
@@ -29,6 +30,7 @@ export function isAllowedOrigin(origin: string | undefined | null, allowedOrigin
29
30
  return allowedOrigins.some((pattern) => matchOriginPattern(origin, pattern));
30
31
  }
31
32
 
33
+ /** Return the request origin if it is in the allowlist; otherwise return a safe `fallback` origin. */
32
34
  export function getValidatedOrigin(
33
35
  origin: string | undefined | null,
34
36
  allowedOrigins: string[],
package/src/output.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** Minimal write sink: anything that accepts a string chunk. */
1
2
  export interface WriteTarget {
2
3
  write(chunk: string): unknown;
3
4
  }
@@ -20,14 +21,21 @@ function writeLine(message: string, target: WriteTarget): void {
20
21
  target.write(`${message}\n`);
21
22
  }
22
23
 
24
+ /** Write a line to a target, defaulting to stdout. */
23
25
  export function echo(message: string, target: WriteTarget = defaultStdoutTarget ?? processStream('stdout')): void {
24
26
  writeLine(message, target);
25
27
  }
26
28
 
29
+ /** Write a line to a target, defaulting to stderr. */
27
30
  export function echoError(message: string, target: WriteTarget = defaultStderrTarget ?? processStream('stderr')): void {
28
31
  writeLine(message, target);
29
32
  }
30
33
 
34
+ /**
35
+ * Override the default stdout/stderr targets.
36
+ *
37
+ * Returns a rollback function that restores the previous targets.
38
+ */
31
39
  export function setDefaultOutputTargets(opts: { stdout?: WriteTarget; stderr?: WriteTarget }): () => void {
32
40
  const prevStdout = defaultStdoutTarget;
33
41
  const prevStderr = defaultStderrTarget;
@@ -39,12 +47,14 @@ export function setDefaultOutputTargets(opts: { stdout?: WriteTarget; stderr?: W
39
47
  };
40
48
  }
41
49
 
50
+ /** In-memory `WriteTarget` that records all chunks for later retrieval. */
42
51
  export interface BufferTarget extends WriteTarget {
43
52
  readonly chunks: string[];
44
53
  text(): string;
45
54
  clear(): void;
46
55
  }
47
56
 
57
+ /** Create an in-memory {@link BufferTarget} for capturing output during tests or tooling. */
48
58
  export function createBufferTarget(): BufferTarget {
49
59
  const chunks: string[] = [];
50
60
  return {