@insureco/bio 0.3.0 → 0.4.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/users.js ADDED
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/users.ts
21
+ var users_exports = {};
22
+ __export(users_exports, {
23
+ BioUsers: () => BioUsers
24
+ });
25
+ module.exports = __toCommonJS(users_exports);
26
+
27
+ // src/errors.ts
28
+ var BioError = class extends Error {
29
+ /** HTTP status code (if from an API response) */
30
+ statusCode;
31
+ /** Machine-readable error code (e.g. 'invalid_grant', 'token_expired') */
32
+ code;
33
+ /** Additional error details from the API */
34
+ details;
35
+ constructor(message, code, statusCode, details) {
36
+ super(message);
37
+ this.name = "BioError";
38
+ this.code = code;
39
+ this.statusCode = statusCode;
40
+ this.details = details;
41
+ }
42
+ };
43
+
44
+ // src/utils.ts
45
+ function retryDelay(attempt) {
46
+ const baseDelay = Math.min(1e3 * 2 ** attempt, 5e3);
47
+ return baseDelay * (0.5 + Math.random() * 0.5);
48
+ }
49
+ function sleep(ms) {
50
+ return new Promise((resolve) => setTimeout(resolve, ms));
51
+ }
52
+ async function parseJsonResponse(response) {
53
+ try {
54
+ return await response.json();
55
+ } catch {
56
+ throw new BioError(
57
+ `Bio-ID returned ${response.status} with non-JSON body`,
58
+ "parse_error",
59
+ response.status
60
+ );
61
+ }
62
+ }
63
+
64
+ // src/users.ts
65
+ var DEFAULT_TIMEOUT_MS = 1e4;
66
+ var BioUsers = class _BioUsers {
67
+ usersUrl;
68
+ serviceKey;
69
+ timeoutMs;
70
+ constructor(config) {
71
+ if (!config.usersUrl) {
72
+ throw new BioError(
73
+ "usersUrl is required \u2014 declare spec.userAccess in catalog-info.yaml and deploy to inject BIO_USERS_URL",
74
+ "config_error"
75
+ );
76
+ }
77
+ if (!config.serviceKey) {
78
+ throw new BioError(
79
+ "serviceKey is required \u2014 declare spec.userAccess in catalog-info.yaml and deploy to inject BIO_SERVICE_KEY",
80
+ "config_error"
81
+ );
82
+ }
83
+ this.usersUrl = config.usersUrl.replace(/\/$/, "");
84
+ this.serviceKey = config.serviceKey;
85
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
86
+ }
87
+ /**
88
+ * Create a BioUsers client from environment variables.
89
+ * Reads BIO_USERS_URL and BIO_SERVICE_KEY (injected by builder on deploy).
90
+ */
91
+ static fromEnv(overrides) {
92
+ return new _BioUsers({
93
+ usersUrl: overrides?.usersUrl ?? process.env.BIO_USERS_URL ?? "",
94
+ serviceKey: overrides?.serviceKey ?? process.env.BIO_SERVICE_KEY ?? "",
95
+ timeoutMs: overrides?.timeoutMs
96
+ });
97
+ }
98
+ /**
99
+ * List members of a specific org.
100
+ *
101
+ * @param orgSlug - The org slug to query
102
+ * @param filters - Optional filters (modules, roles, limit, page)
103
+ */
104
+ async getOrgMembers(orgSlug, filters) {
105
+ const params = buildParams({ orgSlug, ...filters });
106
+ return this.request(params);
107
+ }
108
+ /**
109
+ * List users across orgs with optional filters.
110
+ * Cross-org queries require scope: cross-org in catalog-info.yaml
111
+ * and approval from the target org.
112
+ *
113
+ * @param filters - Optional filters (orgSlug, modules, roles, limit, page)
114
+ */
115
+ async listUsers(filters) {
116
+ const params = buildParams(filters ?? {});
117
+ return this.request(params);
118
+ }
119
+ async request(params, attempt = 0) {
120
+ const url = `${this.usersUrl}?${params.toString()}`;
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
123
+ let response;
124
+ try {
125
+ response = await fetch(url, {
126
+ headers: {
127
+ "x-service-key": this.serviceKey,
128
+ "Content-Type": "application/json"
129
+ },
130
+ signal: controller.signal
131
+ });
132
+ } catch (err) {
133
+ clearTimeout(timer);
134
+ const isTimeout = err instanceof Error && err.name === "AbortError";
135
+ if (!isTimeout && attempt < 2) {
136
+ await sleep(retryDelay(attempt));
137
+ return this.request(params, attempt + 1);
138
+ }
139
+ throw new BioError(
140
+ isTimeout ? "Bio-ID user access request timed out" : `Bio-ID user access request failed: ${String(err)}`,
141
+ isTimeout ? "timeout" : "network_error"
142
+ );
143
+ } finally {
144
+ clearTimeout(timer);
145
+ }
146
+ if (response.status === 403) {
147
+ throw new BioError(
148
+ "Cross-org access denied \u2014 the target org has not approved your service, or scope: cross-org is missing from catalog-info.yaml",
149
+ "access_denied",
150
+ 403
151
+ );
152
+ }
153
+ if (response.status === 401) {
154
+ throw new BioError(
155
+ "Invalid service key \u2014 BIO_SERVICE_KEY may be expired or missing",
156
+ "unauthorized",
157
+ 401
158
+ );
159
+ }
160
+ if (!response.ok) {
161
+ const body2 = await parseJsonResponse(response).catch(() => ({}));
162
+ throw new BioError(
163
+ `Bio-ID user access returned ${response.status}`,
164
+ "api_error",
165
+ response.status,
166
+ body2
167
+ );
168
+ }
169
+ const body = await parseJsonResponse(response);
170
+ return body.data ?? body;
171
+ }
172
+ };
173
+ function buildParams(filters) {
174
+ const params = new URLSearchParams();
175
+ if (filters.orgSlug) params.set("orgSlug", filters.orgSlug);
176
+ if (filters.modules?.length) params.set("modules", filters.modules.join(","));
177
+ if (filters.roles?.length) params.set("roles", filters.roles.join(","));
178
+ if (filters.limit) params.set("limit", String(filters.limit));
179
+ if (filters.page) params.set("page", String(filters.page));
180
+ return params;
181
+ }
182
+ // Annotate the CommonJS export names for ESM import in node:
183
+ 0 && (module.exports = {
184
+ BioUsers
185
+ });
package/dist/users.mjs ADDED
@@ -0,0 +1,128 @@
1
+ import {
2
+ BioError,
3
+ parseJsonResponse,
4
+ retryDelay,
5
+ sleep
6
+ } from "./chunk-NK5VXXWF.mjs";
7
+
8
+ // src/users.ts
9
+ var DEFAULT_TIMEOUT_MS = 1e4;
10
+ var BioUsers = class _BioUsers {
11
+ usersUrl;
12
+ serviceKey;
13
+ timeoutMs;
14
+ constructor(config) {
15
+ if (!config.usersUrl) {
16
+ throw new BioError(
17
+ "usersUrl is required \u2014 declare spec.userAccess in catalog-info.yaml and deploy to inject BIO_USERS_URL",
18
+ "config_error"
19
+ );
20
+ }
21
+ if (!config.serviceKey) {
22
+ throw new BioError(
23
+ "serviceKey is required \u2014 declare spec.userAccess in catalog-info.yaml and deploy to inject BIO_SERVICE_KEY",
24
+ "config_error"
25
+ );
26
+ }
27
+ this.usersUrl = config.usersUrl.replace(/\/$/, "");
28
+ this.serviceKey = config.serviceKey;
29
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
30
+ }
31
+ /**
32
+ * Create a BioUsers client from environment variables.
33
+ * Reads BIO_USERS_URL and BIO_SERVICE_KEY (injected by builder on deploy).
34
+ */
35
+ static fromEnv(overrides) {
36
+ return new _BioUsers({
37
+ usersUrl: overrides?.usersUrl ?? process.env.BIO_USERS_URL ?? "",
38
+ serviceKey: overrides?.serviceKey ?? process.env.BIO_SERVICE_KEY ?? "",
39
+ timeoutMs: overrides?.timeoutMs
40
+ });
41
+ }
42
+ /**
43
+ * List members of a specific org.
44
+ *
45
+ * @param orgSlug - The org slug to query
46
+ * @param filters - Optional filters (modules, roles, limit, page)
47
+ */
48
+ async getOrgMembers(orgSlug, filters) {
49
+ const params = buildParams({ orgSlug, ...filters });
50
+ return this.request(params);
51
+ }
52
+ /**
53
+ * List users across orgs with optional filters.
54
+ * Cross-org queries require scope: cross-org in catalog-info.yaml
55
+ * and approval from the target org.
56
+ *
57
+ * @param filters - Optional filters (orgSlug, modules, roles, limit, page)
58
+ */
59
+ async listUsers(filters) {
60
+ const params = buildParams(filters ?? {});
61
+ return this.request(params);
62
+ }
63
+ async request(params, attempt = 0) {
64
+ const url = `${this.usersUrl}?${params.toString()}`;
65
+ const controller = new AbortController();
66
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
67
+ let response;
68
+ try {
69
+ response = await fetch(url, {
70
+ headers: {
71
+ "x-service-key": this.serviceKey,
72
+ "Content-Type": "application/json"
73
+ },
74
+ signal: controller.signal
75
+ });
76
+ } catch (err) {
77
+ clearTimeout(timer);
78
+ const isTimeout = err instanceof Error && err.name === "AbortError";
79
+ if (!isTimeout && attempt < 2) {
80
+ await sleep(retryDelay(attempt));
81
+ return this.request(params, attempt + 1);
82
+ }
83
+ throw new BioError(
84
+ isTimeout ? "Bio-ID user access request timed out" : `Bio-ID user access request failed: ${String(err)}`,
85
+ isTimeout ? "timeout" : "network_error"
86
+ );
87
+ } finally {
88
+ clearTimeout(timer);
89
+ }
90
+ if (response.status === 403) {
91
+ throw new BioError(
92
+ "Cross-org access denied \u2014 the target org has not approved your service, or scope: cross-org is missing from catalog-info.yaml",
93
+ "access_denied",
94
+ 403
95
+ );
96
+ }
97
+ if (response.status === 401) {
98
+ throw new BioError(
99
+ "Invalid service key \u2014 BIO_SERVICE_KEY may be expired or missing",
100
+ "unauthorized",
101
+ 401
102
+ );
103
+ }
104
+ if (!response.ok) {
105
+ const body2 = await parseJsonResponse(response).catch(() => ({}));
106
+ throw new BioError(
107
+ `Bio-ID user access returned ${response.status}`,
108
+ "api_error",
109
+ response.status,
110
+ body2
111
+ );
112
+ }
113
+ const body = await parseJsonResponse(response);
114
+ return body.data ?? body;
115
+ }
116
+ };
117
+ function buildParams(filters) {
118
+ const params = new URLSearchParams();
119
+ if (filters.orgSlug) params.set("orgSlug", filters.orgSlug);
120
+ if (filters.modules?.length) params.set("modules", filters.modules.join(","));
121
+ if (filters.roles?.length) params.set("roles", filters.roles.join(","));
122
+ if (filters.limit) params.set("limit", String(filters.limit));
123
+ if (filters.page) params.set("page", String(filters.page));
124
+ return params;
125
+ }
126
+ export {
127
+ BioUsers
128
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insureco/bio",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "SDK for Bio-ID SSO integration on the Tawa platform",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,18 +10,16 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.js"
13
+ },
14
+ "./users": {
15
+ "types": "./dist/users.d.ts",
16
+ "import": "./dist/users.mjs",
17
+ "require": "./dist/users.js"
13
18
  }
14
19
  },
15
20
  "files": [
16
21
  "dist"
17
22
  ],
18
- "scripts": {
19
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
- "test": "vitest run",
21
- "test:watch": "vitest",
22
- "lint": "tsc --noEmit",
23
- "prepublishOnly": "npm run build"
24
- },
25
23
  "keywords": [
26
24
  "insureco",
27
25
  "tawa",
@@ -44,5 +42,11 @@
44
42
  },
45
43
  "engines": {
46
44
  "node": ">=18.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts src/users.ts --format cjs,esm --dts --clean",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest",
50
+ "lint": "tsc --noEmit"
47
51
  }
48
- }
52
+ }