@protobase/server 0.23.6 → 0.23.8

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.
@@ -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, layoutItems, linkActionError, promptError, promptFields, updateSetErrors, viewOfList } from "@protobase/schema/internal";
10
+ import { actionInputErrors, addressErrors, addressPlaceholders, boardErrors, chartFields, datesErrors, displayFields, fieldRefsIn, hrefField, hrefPlaceholders, layoutItems, linkActionError, promptError, promptFields, updateSetErrors, viewOfList } 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);
@@ -147,7 +147,7 @@ const listOfErrors = (model, name, models) => {
147
147
  * has a section, related section or sidebar whose `when` condition names what the app does not have, a related section or related list column, a cards display's or gallery's photos or a cards display's favorite button, a `listOf`, a "Save search" button or a
148
148
  * `searchesOf` that do not fit the resources, cards
149
149
  * displays of one list that name different photos, a chart per value that cannot group or add up its fields, a map that cannot show the
150
- * record's position or a map display that cannot find the records in its view, a board whose columns are no filterable enum, a calendar whose dates are no filterable date or timestamp fields, a link to an address the app does not have, an AI action that cannot write its field, action inputs that do not fit, or a create page that is not one of `pages`.
150
+ * record's position or a map display that cannot find the records in its view, a board whose columns are no filterable enum, a calendar or timeline whose dates are no filterable date or timestamp fields, a link to an address the app does not have, an AI action that cannot write its field, action inputs that do not fit, or a create page that is not one of `pages`.
151
151
  */
152
152
  const checkViews = (registry, views, pages = [], fieldTypes = {}) => {
153
153
  const models = Object.fromEntries(registry.entries.map((entry) => [entry.name, entry.model]));
@@ -165,7 +165,7 @@ const checkViews = (registry, views, pages = [], fieldTypes = {}) => {
165
165
  const searches = [...view.saveSearch ? saveSearchErrors(view.saveSearch, view.resource, models, views).map((error) => `saveSearch: ${error}`) : [], ...view.searchesOf ? searchesOfErrors(view.searchesOf, view.resource, models).map((error) => `searchesOf: ${error}`) : []];
166
166
  const maps = [...layoutItems(view.layout).flatMap((item) => item.kind === "map" ? mapErrors(item, model).map((error) => `map "${item.title}": ${error}`) : []), ...(view.list?.displays ?? []).flatMap((display) => display.kind === "map" ? mapDisplayErrors(display, model).map((error) => `display "${display.name}": ${error}`) : [])];
167
167
  const boards = (view.list?.displays ?? []).flatMap((display) => display.kind === "board" ? boardErrors(display, model).map((error) => `display "${display.name}": ${error}`) : []);
168
- const calendars = (view.list?.displays ?? []).flatMap((display) => display.kind === "calendar" ? calendarErrors(display, model).map((error) => `display "${display.name}": ${error}`) : []);
168
+ const calendars = (view.list?.displays ?? []).flatMap((display) => display.kind === "calendar" || display.kind === "timeline" ? datesErrors(display, model).map((error) => `display "${display.name}": ${error}`) : []);
169
169
  const chart = view.chart ? chartErrors(view.chart, model).map((error) => `chart: ${error}`) : [];
170
170
  const history = model.history ? [] : layoutItems(view.layout).flatMap((item) => item.kind === "history" ? [`history "${item.title}" needs .history() on resource "${view.resource}"`] : []);
171
171
  const comments = model.comments ? [] : layoutItems(view.layout).flatMap((item) => item.kind === "comments" ? [`comments "${item.title}" needs .comments() on resource "${view.resource}"`] : []);
@@ -53,6 +53,7 @@ import { batchRoute } from "./routes/batch.js";
53
53
  import { collectionRoutes } from "./routes/collection.js";
54
54
  import { docsRoutes } from "./routes/docs.js";
55
55
  import { metaRoutes } from "./routes/meta.js";
56
+ import { webManifestRoute } from "./web-app/web-manifest-route.js";
56
57
  import { recordRoutes } from "./routes/record.js";
57
58
  import { createScanGuard } from "./scan-guard.js";
58
59
  import { failedJobs, failedJobsView } from "./jobs/failed-jobs.js";
@@ -177,7 +178,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
177
178
  ...options.onUnhandledError && { report: options.onUnhandledError }
178
179
  } }
179
180
  };
180
- const meta = createMeta(deps, auth?.appName ?? readAppName(globalThis.process?.env ?? {}) ?? "Protobase");
181
+ const appName = auth?.appName ?? readAppName(globalThis.process?.env ?? {}) ?? "Protobase";
182
+ const meta = createMeta(deps, appName);
181
183
  const onError = createErrorHandler(options.onUnhandledError);
182
184
  const guarded = (routes, caller = authenticate) => {
183
185
  const app = new Hono();
@@ -265,6 +267,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
265
267
  }
266
268
  app.route(basePath, api);
267
269
  app.route(`${deps.systemPath}/meta`, metaApp);
270
+ app.route(systemPath || "/", webManifestRoute(appName));
268
271
  app.route(deps.systemPath || "/", system);
269
272
  const webhooks = createWebhooks({
270
273
  deps,
@@ -120,6 +120,16 @@ const listShape = (resource, model, models, prompted) => shapes.object({
120
120
  end: shapes.field,
121
121
  title: shapes.field
122
122
  }, { requires: ["start"] }),
123
+ timeline: shapes.object({
124
+ kind: shapes.data,
125
+ name: shapes.data,
126
+ label: shapes.data,
127
+ icon: shapes.data,
128
+ start: shapes.field,
129
+ end: shapes.field,
130
+ group: shapes.field,
131
+ title: shapes.field
132
+ }, { requires: ["start"] }),
123
133
  custom: shapes.object({
124
134
  kind: shapes.data,
125
135
  name: shapes.data,
@@ -0,0 +1,17 @@
1
+ import { webManifest } from "./web-manifest.js";
2
+ import { Hono } from "hono";
3
+ //#region src/web-app/web-manifest-route.ts
4
+ /** The path under the system path (`/api`) the admin app's page links its manifest at. */
5
+ const webManifestPath = "/manifest.webmanifest";
6
+ /** Answers the web app manifest to anyone: the browser fetches it without the user's token, before they sign in. */
7
+ const webManifestRoute = (appName) => {
8
+ const app = new Hono();
9
+ const body = JSON.stringify(webManifest(appName));
10
+ app.get(webManifestPath, () => new Response(body, { headers: {
11
+ "content-type": "application/manifest+json",
12
+ "cache-control": "no-cache"
13
+ } }));
14
+ return app;
15
+ };
16
+ //#endregion
17
+ export { webManifestPath, webManifestRoute };
@@ -0,0 +1,22 @@
1
+ //#region src/web-app/web-manifest.ts
2
+ /** Where the app's favicon is on its own origin, which `protobase build` and `protobase dev` always answer. */
3
+ const faviconPath = "/protobase.favicon.svg";
4
+ /**
5
+ * The web app manifest that makes the admin app installable: named for the app, opening at its home page in a window
6
+ * of its own, with the favicon as its icon. An SVG with `sizes: "any"` meets Chrome's 192 and 512 pixel icon sizes.
7
+ */
8
+ const webManifest = (appName) => ({
9
+ name: appName,
10
+ short_name: appName,
11
+ id: "/",
12
+ start_url: "/",
13
+ scope: "/",
14
+ display: "standalone",
15
+ icons: [{
16
+ src: faviconPath,
17
+ sizes: "any",
18
+ type: "image/svg+xml"
19
+ }]
20
+ });
21
+ //#endregion
22
+ export { faviconPath, webManifest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.23.6",
3
+ "version": "0.23.8",
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.23.6",
35
- "@protobase/query": "0.23.6",
36
- "@protobase/schema": "0.23.6",
34
+ "@protobase/layout": "0.23.8",
35
+ "@protobase/query": "0.23.8",
36
+ "@protobase/schema": "0.23.8",
37
37
  "@react-email/render": "^2.1.0",
38
38
  "@scalar/hono-api-reference": "^0.12.10",
39
39
  "@sentry/core": "^11.7.0",