apiblaze 0.18.2 → 0.18.6

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.
@@ -73,6 +73,23 @@ interface ApiblazeGroupsConfig {
73
73
  }
74
74
  type GroupsAction = {
75
75
  action: 'snapshot';
76
+ } | {
77
+ action: 'list-groups';
78
+ offset?: number;
79
+ q?: string;
80
+ } | {
81
+ action: 'search-users';
82
+ q?: string;
83
+ limit?: number;
84
+ offset?: number;
85
+ } | {
86
+ action: 'list-observed';
87
+ offset?: number;
88
+ q?: string;
89
+ } | {
90
+ action: 'group-members';
91
+ groupId: string;
92
+ offset?: number;
76
93
  } | {
77
94
  action: 'create-group';
78
95
  name: string;
@@ -116,6 +133,18 @@ type GroupsAction = {
116
133
  email?: string;
117
134
  displayName?: string;
118
135
  groups?: string[];
136
+ } | {
137
+ action: 'create-user';
138
+ ref: string;
139
+ displayName?: string;
140
+ } | {
141
+ action: 'list-admins';
142
+ } | {
143
+ action: 'add-admin';
144
+ email: string;
145
+ } | {
146
+ action: 'remove-admin';
147
+ email: string;
119
148
  };
120
149
  declare function createApiblazeGroups(cfg: ApiblazeGroupsConfig): {
121
150
  handler: (req: Request) => Promise<Response>;
@@ -73,6 +73,23 @@ interface ApiblazeGroupsConfig {
73
73
  }
74
74
  type GroupsAction = {
75
75
  action: 'snapshot';
76
+ } | {
77
+ action: 'list-groups';
78
+ offset?: number;
79
+ q?: string;
80
+ } | {
81
+ action: 'search-users';
82
+ q?: string;
83
+ limit?: number;
84
+ offset?: number;
85
+ } | {
86
+ action: 'list-observed';
87
+ offset?: number;
88
+ q?: string;
89
+ } | {
90
+ action: 'group-members';
91
+ groupId: string;
92
+ offset?: number;
76
93
  } | {
77
94
  action: 'create-group';
78
95
  name: string;
@@ -116,6 +133,18 @@ type GroupsAction = {
116
133
  email?: string;
117
134
  displayName?: string;
118
135
  groups?: string[];
136
+ } | {
137
+ action: 'create-user';
138
+ ref: string;
139
+ displayName?: string;
140
+ } | {
141
+ action: 'list-admins';
142
+ } | {
143
+ action: 'add-admin';
144
+ email: string;
145
+ } | {
146
+ action: 'remove-admin';
147
+ email: string;
119
148
  };
120
149
  declare function createApiblazeGroups(cfg: ApiblazeGroupsConfig): {
121
150
  handler: (req: Request) => Promise<Response>;
@@ -89,7 +89,14 @@ function createPlaneCore(cfg) {
89
89
  function createApiblazeGroups(cfg) {
90
90
  if (typeof cfg.getUser !== "function") throw new Error("apiblaze: getUser is required \u2014 map your session to { tenant, userId, email }.");
91
91
  const core = createPlaneCore({ cpKey: cfg.cpKey, apikeysBase: cfg.base, iamBase: cfg.iamBase });
92
- const iam = (method, path, user, tenant, body) => core.planeFetch(core.iamBase, method, `/v1/admin${path}`, user, { query: { tenant }, body });
92
+ const iam = (method, path, user, tenant, body, query) => core.planeFetch(core.iamBase, method, `/v1/admin${path}`, user, { query: { tenant, ...query ?? {} }, body });
93
+ const paging = (o) => {
94
+ const p = {};
95
+ if (typeof o.limit === "number") p.limit = String(o.limit);
96
+ if (typeof o.offset === "number") p.offset = String(o.offset);
97
+ if (o.q != null && o.q !== "") p.q = o.q;
98
+ return p;
99
+ };
93
100
  async function context(user) {
94
101
  const tenant = await core.resolveTenant(user);
95
102
  await core.ensureEndUser(user, tenant);
@@ -102,22 +109,28 @@ function createApiblazeGroups(cfg) {
102
109
  async function run(user, op) {
103
110
  const { tenant } = await context(user);
104
111
  if (op.action === "snapshot") {
105
- const [groupsRes, usersRes, observedRes] = await Promise.all([
106
- iam("GET", "/groups", user, tenant),
107
- iam("GET", "/users", user, tenant),
108
- iam("GET", "/observed-identities", user, tenant)
112
+ const [groupsRes, observedRes] = await Promise.all([
113
+ iam("GET", "/groups", user, tenant, void 0, { limit: "25" }),
114
+ iam("GET", "/observed-identities", user, tenant, void 0, { limit: "1" })
109
115
  ]);
110
- if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], users: [], observed: [] } };
116
+ if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, observedNew: 0 } };
111
117
  if (!groupsRes.ok) return { status: groupsRes.status, data: { error: await safeErr(groupsRes) } };
112
118
  const g = await groupsRes.json();
113
- const u = usersRes.ok ? await usersRes.json() : { users: [] };
114
119
  const o = observedRes.ok ? await observedRes.json() : {};
115
120
  return {
116
121
  status: 200,
117
- data: { access: "ok", groups: g.groups ?? [], users: u.users ?? [], observed: o.identities ?? o.observed ?? [] }
122
+ data: { access: "ok", groups: g.groups ?? [], groupTotal: g.total ?? (g.groups?.length ?? 0), observedNew: o.total ?? 0 }
118
123
  };
119
124
  }
120
125
  switch (op.action) {
126
+ case "list-groups":
127
+ return pass(await iam("GET", "/groups", user, tenant, void 0, paging({ limit: 25, offset: op.offset, q: op.q })));
128
+ case "search-users":
129
+ return pass(await iam("GET", "/users", user, tenant, void 0, paging({ limit: op.limit ?? 20, offset: op.offset, q: op.q })));
130
+ case "list-observed":
131
+ return pass(await iam("GET", "/observed-identities", user, tenant, void 0, paging({ limit: 25, offset: op.offset, q: op.q })));
132
+ case "group-members":
133
+ return pass(await iam("GET", `/groups/${encodeURIComponent(op.groupId)}`, user, tenant, void 0, paging({ limit: 50, offset: op.offset })));
121
134
  case "group-detail": {
122
135
  const [detail, subs] = await Promise.all([
123
136
  iam("GET", `/groups/${encodeURIComponent(op.groupId)}`, user, tenant),
@@ -172,6 +185,23 @@ function createApiblazeGroups(cfg) {
172
185
  display_name: op.displayName,
173
186
  groups: op.groups ?? []
174
187
  }));
188
+ case "create-user": {
189
+ const ref = (op.ref ?? "").trim();
190
+ if (!ref) return { status: 400, data: { error: "Enter an email or a user id" } };
191
+ if (ref.includes("@")) {
192
+ return pass(await iam("POST", "/users", user, tenant, { email: ref, display_name: op.displayName }));
193
+ }
194
+ return pass(await iam("POST", "/identities/upsert", user, tenant, { consumer_user_id: ref, display_name: op.displayName }));
195
+ }
196
+ // Tenant-admin allowlist — appoint/remove CO-admins by email. This is the
197
+ // ONLY way admin membership changes (apiblaze_admins is sealed); the plane
198
+ // enforces the guardrails (producer-pinned + last-admin).
199
+ case "list-admins":
200
+ return pass(await iam("GET", "/admin-emails", user, tenant));
201
+ case "add-admin":
202
+ return pass(await iam("POST", "/admin-emails", user, tenant, { email: op.email }));
203
+ case "remove-admin":
204
+ return pass(await iam("DELETE", `/admin-emails/${encodeURIComponent(op.email)}`, user, tenant));
175
205
  }
176
206
  }
177
207
  async function handler(req) {
@@ -61,7 +61,14 @@ function createPlaneCore(cfg) {
61
61
  function createApiblazeGroups(cfg) {
62
62
  if (typeof cfg.getUser !== "function") throw new Error("apiblaze: getUser is required \u2014 map your session to { tenant, userId, email }.");
63
63
  const core = createPlaneCore({ cpKey: cfg.cpKey, apikeysBase: cfg.base, iamBase: cfg.iamBase });
64
- const iam = (method, path, user, tenant, body) => core.planeFetch(core.iamBase, method, `/v1/admin${path}`, user, { query: { tenant }, body });
64
+ const iam = (method, path, user, tenant, body, query) => core.planeFetch(core.iamBase, method, `/v1/admin${path}`, user, { query: { tenant, ...query ?? {} }, body });
65
+ const paging = (o) => {
66
+ const p = {};
67
+ if (typeof o.limit === "number") p.limit = String(o.limit);
68
+ if (typeof o.offset === "number") p.offset = String(o.offset);
69
+ if (o.q != null && o.q !== "") p.q = o.q;
70
+ return p;
71
+ };
65
72
  async function context(user) {
66
73
  const tenant = await core.resolveTenant(user);
67
74
  await core.ensureEndUser(user, tenant);
@@ -74,22 +81,28 @@ function createApiblazeGroups(cfg) {
74
81
  async function run(user, op) {
75
82
  const { tenant } = await context(user);
76
83
  if (op.action === "snapshot") {
77
- const [groupsRes, usersRes, observedRes] = await Promise.all([
78
- iam("GET", "/groups", user, tenant),
79
- iam("GET", "/users", user, tenant),
80
- iam("GET", "/observed-identities", user, tenant)
84
+ const [groupsRes, observedRes] = await Promise.all([
85
+ iam("GET", "/groups", user, tenant, void 0, { limit: "25" }),
86
+ iam("GET", "/observed-identities", user, tenant, void 0, { limit: "1" })
81
87
  ]);
82
- if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], users: [], observed: [] } };
88
+ if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, observedNew: 0 } };
83
89
  if (!groupsRes.ok) return { status: groupsRes.status, data: { error: await safeErr(groupsRes) } };
84
90
  const g = await groupsRes.json();
85
- const u = usersRes.ok ? await usersRes.json() : { users: [] };
86
91
  const o = observedRes.ok ? await observedRes.json() : {};
87
92
  return {
88
93
  status: 200,
89
- data: { access: "ok", groups: g.groups ?? [], users: u.users ?? [], observed: o.identities ?? o.observed ?? [] }
94
+ data: { access: "ok", groups: g.groups ?? [], groupTotal: g.total ?? (g.groups?.length ?? 0), observedNew: o.total ?? 0 }
90
95
  };
91
96
  }
92
97
  switch (op.action) {
98
+ case "list-groups":
99
+ return pass(await iam("GET", "/groups", user, tenant, void 0, paging({ limit: 25, offset: op.offset, q: op.q })));
100
+ case "search-users":
101
+ return pass(await iam("GET", "/users", user, tenant, void 0, paging({ limit: op.limit ?? 20, offset: op.offset, q: op.q })));
102
+ case "list-observed":
103
+ return pass(await iam("GET", "/observed-identities", user, tenant, void 0, paging({ limit: 25, offset: op.offset, q: op.q })));
104
+ case "group-members":
105
+ return pass(await iam("GET", `/groups/${encodeURIComponent(op.groupId)}`, user, tenant, void 0, paging({ limit: 50, offset: op.offset })));
93
106
  case "group-detail": {
94
107
  const [detail, subs] = await Promise.all([
95
108
  iam("GET", `/groups/${encodeURIComponent(op.groupId)}`, user, tenant),
@@ -144,6 +157,23 @@ function createApiblazeGroups(cfg) {
144
157
  display_name: op.displayName,
145
158
  groups: op.groups ?? []
146
159
  }));
160
+ case "create-user": {
161
+ const ref = (op.ref ?? "").trim();
162
+ if (!ref) return { status: 400, data: { error: "Enter an email or a user id" } };
163
+ if (ref.includes("@")) {
164
+ return pass(await iam("POST", "/users", user, tenant, { email: ref, display_name: op.displayName }));
165
+ }
166
+ return pass(await iam("POST", "/identities/upsert", user, tenant, { consumer_user_id: ref, display_name: op.displayName }));
167
+ }
168
+ // Tenant-admin allowlist — appoint/remove CO-admins by email. This is the
169
+ // ONLY way admin membership changes (apiblaze_admins is sealed); the plane
170
+ // enforces the guardrails (producer-pinned + last-admin).
171
+ case "list-admins":
172
+ return pass(await iam("GET", "/admin-emails", user, tenant));
173
+ case "add-admin":
174
+ return pass(await iam("POST", "/admin-emails", user, tenant, { email: op.email }));
175
+ case "remove-admin":
176
+ return pass(await iam("DELETE", `/admin-emails/${encodeURIComponent(op.email)}`, user, tenant));
147
177
  }
148
178
  }
149
179
  async function handler(req) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.18.2",
3
+ "version": "0.18.6",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",