@protobase/server 0.21.11 → 0.22.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.
@@ -5149,6 +5149,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5149
5149
  }>>[];
5150
5150
  query: import("zod").ZodObject<{
5151
5151
  query: import("zod").ZodDefault<import("zod").ZodString>;
5152
+ offset: import("zod").ZodDefault<import("zod").ZodCoercedNumber<unknown>>;
5152
5153
  }, import("zod/v4/core").$strip>;
5153
5154
  }, {
5154
5155
  organizations: {
@@ -5156,6 +5157,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5156
5157
  name: string;
5157
5158
  slug: string;
5158
5159
  }[];
5160
+ total: number;
5159
5161
  }>;
5160
5162
  getInvitation: import("better-auth").StrictEndpoint<"/invitation", {
5161
5163
  method: "GET";
@@ -8,7 +8,7 @@ const memberNotFound = {
8
8
  code: "MEMBER_NOT_FOUND",
9
9
  message: "There is no such member."
10
10
  };
11
- const searchLimit = 20;
11
+ const pageSize = 20;
12
12
  /**
13
13
  * Protobase's member endpoints beside Better Auth's: changing a member's app roles, handing ownership over in one step,
14
14
  * and, for people with a global role, finding any organization to work in.
@@ -144,38 +144,55 @@ const memberEndpoints = ({ resolved }) => ({
144
144
  });
145
145
  }),
146
146
  /**
147
- * `GET /organization/search?query=`, for someone with a global role, who may work in any organization: the first
148
- * organizations whose name or slug contains the text, by name.
147
+ * `GET /organization/search?query=&offset=`, for someone with a global role, who may work in any organization: every
148
+ * organization by slug, a page at a time, narrowed by the text in its name or slug, with the total.
149
149
  */
150
150
  searchOrganizations: createAuthEndpoint("/organization/search", {
151
151
  method: "GET",
152
152
  use: [sessionMiddleware],
153
- query: z$1.object({ query: z$1.string().default("") })
153
+ query: z$1.object({
154
+ query: z$1.string().default(""),
155
+ offset: z$1.coerce.number().int().min(0).default(0)
156
+ })
154
157
  }, async (ctx) => {
155
158
  if (globalRoles(ctx.context.session.user.role).length === 0) throw new APIError("FORBIDDEN", {
156
159
  code: "GLOBAL_ROLE_REQUIRED",
157
160
  message: "Only people with a global role can open organizations they are not a member of."
158
161
  });
159
162
  const text = ctx.query.query.trim();
160
- const find = (field) => ctx.context.adapter.findMany({
163
+ const where = text ? [{
164
+ field: "name",
165
+ operator: "contains",
166
+ value: text,
167
+ connector: "OR"
168
+ }, {
169
+ field: "slug",
170
+ operator: "contains",
171
+ value: text,
172
+ connector: "OR"
173
+ }] : void 0;
174
+ const organizations = await ctx.context.adapter.findMany({
161
175
  model: "organization",
162
- ...text && { where: [{
163
- field,
164
- value: text,
165
- operator: "contains"
166
- }] },
176
+ where,
167
177
  sortBy: {
168
- field: "name",
178
+ field: "slug",
169
179
  direction: "asc"
170
180
  },
171
- limit: searchLimit
172
- });
173
- const organizations = [...new Map([...await find("name"), ...text ? await find("slug") : []].map((organization) => [organization.id, organization])).values()].sort((a, b) => a.name.localeCompare(b.name)).slice(0, searchLimit);
174
- return ctx.json({ organizations: organizations.map(({ id, name, slug }) => ({
175
- id,
176
- name,
177
- slug
178
- })) });
181
+ limit: pageSize,
182
+ offset: ctx.query.offset
183
+ });
184
+ const total = await ctx.context.adapter.count({
185
+ model: "organization",
186
+ where
187
+ });
188
+ return ctx.json({
189
+ organizations: organizations.map(({ id, name, slug }) => ({
190
+ id,
191
+ name,
192
+ slug
193
+ })),
194
+ total
195
+ });
179
196
  })
180
197
  });
181
198
  //#endregion
@@ -7,7 +7,7 @@ import { mapDisplayErrors, mapErrors } from "./view-map.js";
7
7
  import { relatedColumnErrors } from "./view-related-column.js";
8
8
  import { editableErrors } from "./view-editable.js";
9
9
  import { conditionProblems } from "@protobase/layout";
10
- import { actionInputErrors, addressErrors, addressPlaceholders, boardErrors, calendarErrors, chartFields, displayFields, fieldRefsIn, hrefField, hrefPlaceholders, linkActionError, promptError, promptFields } from "@protobase/schema/internal";
10
+ import { actionInputErrors, addressErrors, addressPlaceholders, boardErrors, calendarErrors, chartFields, displayFields, fieldRefsIn, hrefField, hrefPlaceholders, layoutItems, linkActionError, promptError, promptFields } from "@protobase/schema/internal";
11
11
  import { isRecordCondition } from "@protobase/schema";
12
12
  //#region src/check-views.ts
13
13
  const linkFields = (run) => run?.kind !== "link" ? [] : "to" in run ? addressPlaceholders(run.to) : hrefPlaceholders(run.href);
@@ -152,7 +152,7 @@ const checkViews = (registry, views, pages = [], fieldTypes = {}) => {
152
152
  const problems = views.flatMap((view) => {
153
153
  const model = models[view.resource];
154
154
  if (!model) return [];
155
- const related = [...view.layout.flatMap((item) => item.kind === "related" ? relatedErrors(item, view.resource, models).map((error) => `related "${item.title}": ${error}`) : []), ...relatedColumnsErrors(view, model, models)];
155
+ const related = [...layoutItems(view.layout).flatMap((item) => item.kind === "related" ? relatedErrors(item, view.resource, models).map((error) => `related "${item.title}": ${error}`) : []), ...relatedColumnsErrors(view, model, models)];
156
156
  const photos = [
157
157
  ...(view.list?.displays ?? []).flatMap((display) => display.kind === "cards" && display.photos ? cardPhotosErrors(display.photos, view.resource, models).map((error) => `display "${display.name}" photos: ${error}`) : []),
158
158
  ...sharedPhotosErrors(view),
@@ -158,52 +158,67 @@ const widgetShape = shapes.oneOf({
158
158
  label: shapes.data
159
159
  }, { requires: ["fields"] })
160
160
  });
161
- const layoutShape = (resource, model, models) => shapes.oneOf({
162
- section: shapes.object({
161
+ const layoutShape = (resource, model, models) => {
162
+ const when = recordWhenShape(resource, models);
163
+ const section = shapes.object({
163
164
  kind: shapes.data,
164
165
  title: shapes.data,
165
166
  help: shapes.data,
166
167
  helpWhen: shapes.data,
167
168
  presentation: shapes.data,
168
- when: recordWhenShape(resource, models),
169
+ when,
169
170
  fields: shapes.list(shapes.field, { nonEmpty: true })
170
- }, { requires: ["fields", "when"] }),
171
- sidebar: shapes.object({
172
- kind: shapes.data,
173
- fields: shapes.list(shapes.field, { nonEmpty: true }),
174
- when: recordWhenShape(resource, models)
175
- }, { requires: ["fields", "when"] }),
176
- related: shapes.map((item) => pruneRelated(item, resource, models)),
177
- custom: shapes.object({
178
- kind: shapes.data,
179
- component: shapes.data,
180
- title: shapes.data,
181
- sidebar: shapes.data,
182
- when: shapes.data,
183
- props: shapes.refs
184
- }, { requires: ["props"] }),
185
- map: shapes.object({
186
- kind: shapes.data,
187
- title: shapes.data,
188
- latitude: shapes.field,
189
- longitude: shapes.field,
190
- zoom: shapes.data,
191
- sidebar: shapes.data,
192
- when: shapes.data
193
- }, { requires: ["latitude", "longitude"] }),
194
- gallery: shapes.map((item) => pruneGallery(item, resource, model, models)),
195
- history: shapes.object({
196
- kind: shapes.data,
197
- title: shapes.data,
198
- limit: shapes.data,
199
- when: shapes.data
200
- }),
201
- comments: shapes.object({
202
- kind: shapes.data,
203
- title: shapes.data,
204
- when: shapes.data
205
- })
206
- });
171
+ }, { requires: ["fields", "when"] });
172
+ const related = shapes.map((item) => pruneRelated(item, resource, models));
173
+ return shapes.oneOf({
174
+ section,
175
+ sidebar: shapes.object({
176
+ kind: shapes.data,
177
+ fields: shapes.list(shapes.field, { nonEmpty: true }),
178
+ when
179
+ }, { requires: ["fields", "when"] }),
180
+ related,
181
+ group: shapes.object({
182
+ kind: shapes.data,
183
+ title: shapes.data,
184
+ collapsed: shapes.data,
185
+ when,
186
+ items: shapes.list(shapes.oneOf({
187
+ section,
188
+ related
189
+ }), { nonEmpty: true })
190
+ }, { requires: ["items", "when"] }),
191
+ custom: shapes.object({
192
+ kind: shapes.data,
193
+ component: shapes.data,
194
+ title: shapes.data,
195
+ sidebar: shapes.data,
196
+ when: shapes.data,
197
+ props: shapes.refs
198
+ }, { requires: ["props"] }),
199
+ map: shapes.object({
200
+ kind: shapes.data,
201
+ title: shapes.data,
202
+ latitude: shapes.field,
203
+ longitude: shapes.field,
204
+ zoom: shapes.data,
205
+ sidebar: shapes.data,
206
+ when: shapes.data
207
+ }, { requires: ["latitude", "longitude"] }),
208
+ gallery: shapes.map((item) => pruneGallery(item, resource, model, models)),
209
+ history: shapes.object({
210
+ kind: shapes.data,
211
+ title: shapes.data,
212
+ limit: shapes.data,
213
+ when: shapes.data
214
+ }),
215
+ comments: shapes.object({
216
+ kind: shapes.data,
217
+ title: shapes.data,
218
+ when: shapes.data
219
+ })
220
+ });
221
+ };
207
222
  const linkShape = (model, models) => shapes.map((run) => {
208
223
  return ("to" in run ? addressErrors(run.to, {
209
224
  models,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.21.11",
3
+ "version": "0.22.0",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -31,9 +31,9 @@
31
31
  "dependencies": {
32
32
  "@better-auth/passkey": "^1.7.7",
33
33
  "@better-auth/sso": "^1.7.7",
34
- "@protobase/layout": "0.21.11",
35
- "@protobase/query": "0.21.11",
36
- "@protobase/schema": "0.21.11",
34
+ "@protobase/layout": "0.22.0",
35
+ "@protobase/query": "0.22.0",
36
+ "@protobase/schema": "0.22.0",
37
37
  "@react-email/render": "^2.1.0",
38
38
  "@scalar/hono-api-reference": "^0.12.10",
39
39
  "@sentry/core": "^11.7.0",