@protobase/server 0.19.0 → 0.19.1

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.
@@ -59,8 +59,8 @@ export type AdminOptions = {
59
59
  */
60
60
  maps?: MapOptions | false;
61
61
  /**
62
- * The plugins the admin app loads (see `PluginOption`), before the ones the platform lists in `PROTOBASE_PLUGINS`;
63
- * `false` turns both off.
62
+ * The plugins the admin app loads (see `PluginOption`), before the ones the platform lists in `PROTOBASE_PLUGINS`
63
+ * and, for those that load before sign-in, `PROTOBASE_PLUGINS_BEFORE_SIGN_IN`; `false` turns all of them off.
64
64
  */
65
65
  plugins?: PluginOption[] | false;
66
66
  /**
@@ -14,9 +14,10 @@ import { Hono } from "hono";
14
14
  * operator provider's name, when staff can sign in as people; `passkeyDomains`, when people have passkeys for more than
15
15
  * one of the app's hosts, the RP IDs a passkey sign-in can ask for, the passkey domain first; with organizations, who may create them and the roles
16
16
  * with their labels, `membership` for those a member can hold, and the roles each one's holders may also give; and
17
- * `anonymous` when the project has an anonymous role, so the admin app opens for visitors before anyone signs in.
17
+ * `anonymous` when the project has an anonymous role, so the admin app opens for visitors before anyone signs in; and
18
+ * `plugins`, those the app loads before sign-in (see `PluginOption`).
18
19
  */
19
- const statusRoute = (auth, { anonymous = false } = {}) => {
20
+ const statusRoute = (auth, { anonymous = false, plugins = [] } = {}) => {
20
21
  const app = new Hono();
21
22
  app.get("/status", async (c) => {
22
23
  const context = await auth.$context;
@@ -41,6 +42,7 @@ const statusRoute = (auth, { anonymous = false } = {}) => {
41
42
  ...staffSignIn && { staffSignIn },
42
43
  ...passkeyDomains.length > 1 && { passkeyDomains },
43
44
  ...anonymous && { anonymous },
45
+ ...plugins.length > 0 && { plugins },
44
46
  ...auth.organizations && { organizations: {
45
47
  create: auth.organizations.create,
46
48
  roles: auth.roleDefinitions.names.map((name) => ({
@@ -23,7 +23,7 @@ import { createFilesRuntime } from "./files/runtime.js";
23
23
  import { fileRoutes } from "./files/routes.js";
24
24
  import { functionRoutes } from "./functions/routes.js";
25
25
  import { mapStyleUrl } from "./maps/map-settings.js";
26
- import { pluginOptions } from "./plugins/plugin-settings.js";
26
+ import { pluginOptions, pluginsBeforeSignIn } from "./plugins/plugin-settings.js";
27
27
  import { runtimeUrl } from "./runtime/runtime-settings.js";
28
28
  import { checkSourceNames, sourceOptions } from "./sources/source-settings.js";
29
29
  import { createMeta } from "./meta.js";
@@ -148,7 +148,10 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
148
148
  }));
149
149
  }
150
150
  if (auth) {
151
- app.route(authBasePath, statusRoute(auth, { anonymous: Object.hasOwn(options.roles?.definitions ?? {}, anonymousRole) }));
151
+ app.route(authBasePath, statusRoute(auth, {
152
+ anonymous: Object.hasOwn(options.roles?.definitions ?? {}, anonymousRole),
153
+ plugins: pluginsBeforeSignIn(plugins)
154
+ }));
152
155
  if (auth.organizations) app.route(authBasePath, organizationRoutes({
153
156
  auth,
154
157
  audit: deps.audit,
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ import { FilesOptions } from "./files/options.js";
20
20
  import { ErrorReportingOptions, ErrorReportingSettings, errorReportingSettings, errorReportingVariable } from "./error-reporting/error-reporting-settings.js";
21
21
  import { CleanupEntry, CleanupSchedule, ScheduledDelete, storageCleanupSchedule } from "./files/cleanup-schedule.js";
22
22
  import { CleanupReport } from "./files/cleanup-runner.js";
23
- import { PluginOption, PluginRef, pluginOptions, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
23
+ import { PluginOption, PluginRef, beforeSignInPluginsVariable, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
24
24
  import { ResourceSource, configExports } from "./resource-source.js";
25
25
  import { SourceOption, SourceRef, checkSourceNames, sourceOptions, sourcesFor, sourcesVariable } from "./sources/source-settings.js";
26
26
  import { RecordKeyInput, ToolRecord, ToolRecords } from "./assistant/records.js";
@@ -83,4 +83,4 @@ import { FatalJobError } from "./jobs/run-job.js";
83
83
  import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
84
84
  import { jobsDrainApp } from "./jobs/drain-route.js";
85
85
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
86
- export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapOptions, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
86
+ export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapOptions, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import { limitedFileStore } from "./files/limits.js";
36
36
  import { localFileStore, localFiles } from "./files/local-files.js";
37
37
  import { s3FileStore, s3Files } from "./files/s3-files.js";
38
38
  import { mapStyleUrl, mapStyleVariable } from "./maps/map-settings.js";
39
- import { pluginOptions, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
39
+ import { beforeSignInPluginsVariable, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
40
40
  import { runtimeUrl, runtimeVariable, seesRuntime } from "./runtime/runtime-settings.js";
41
41
  import { checkSourceNames, sourceOptions, sourcesFor, sourcesVariable } from "./sources/source-settings.js";
42
42
  import { renderVariant, thumbHash } from "./files/images.js";
@@ -64,4 +64,4 @@ import { aspectRatio, fileSize, fileType, imageSize, processor } from "./files/p
64
64
  import { defineEmail } from "./mail/define-email.js";
65
65
  import { startJobs } from "./jobs/runtime.js";
66
66
  import { jobsDrainApp } from "./jobs/drain-route.js";
67
- export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
67
+ export { FatalJobError, HttpProblem, MissingCredentials, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
@@ -3,12 +3,15 @@ import { JsonValue } from "@protobase/schema";
3
3
  /**
4
4
  * A plugin the admin app loads at runtime: `url` names its plugin.json (`protobase build-plugin` writes it), on another
5
5
  * origin or this one (`/plugins/...`). The plugin reads `settings` with `usePlugin()`. With `roles`, `/meta` names it
6
- * only to callers holding one of them; without, to everyone.
6
+ * only to callers holding one of them; without, to everyone. With `beforeSignIn`, the sign-in status names it to
7
+ * everyone, so the app loads it before anyone signs in and draws its `beforeSignIn` component around the pages someone
8
+ * signed out sees; it cannot have `roles`, since someone signed out has none.
7
9
  */
8
10
  export type PluginOption = {
9
11
  url: string;
10
12
  settings?: Record<string, JsonValue>;
11
13
  roles?: string[];
14
+ beforeSignIn?: boolean;
12
15
  };
13
16
  /** A plugin as `/meta` names it to a caller. */
14
17
  export type PluginRef = {
@@ -17,12 +20,17 @@ export type PluginRef = {
17
20
  };
18
21
  /** The platform's plugins, as plugin.json URLs separated by commas, loaded after the ones `options.plugins` lists. */
19
22
  export declare const pluginsVariable = "PROTOBASE_PLUGINS";
23
+ /** The platform's plugins that load before sign-in, as `PROTOBASE_PLUGINS` lists them, after the ones it lists. */
24
+ export declare const beforeSignInPluginsVariable = "PROTOBASE_PLUGINS_BEFORE_SIGN_IN";
20
25
  /**
21
26
  * The plugins the app loads: the `bundled` ones its bundle carries, then those `options.plugins` lists, then those
22
- * `PROTOBASE_PLUGINS` adds, each URL once. `false` turns off the last two; the bundle's are the app's own UI. A value
23
- * that is no URL stops the server at startup.
27
+ * `PROTOBASE_PLUGINS` and `PROTOBASE_PLUGINS_BEFORE_SIGN_IN` add, each URL once, where it is first listed. A URL either
28
+ * of the last two lists loads before sign-in. `false` turns off all but the bundle's, which are the app's own UI. A
29
+ * value that is no URL, or a plugin that loads before sign-in only for some roles, stops the server at startup.
24
30
  */
25
31
  export declare const pluginOptions: (option: PluginOption[] | false | undefined, env: Record<string, string | undefined>, bundled?: readonly string[]) => PluginOption[];
26
32
  /** The plugins `/meta` names to a caller with `roles`: those for everyone, and those for one of their roles. */
27
33
  export declare const pluginsFor: (plugins: readonly PluginOption[], roles: readonly string[]) => PluginRef[];
34
+ /** The plugins the sign-in status names to everyone, for the app to load before anyone signs in. */
35
+ export declare const pluginsBeforeSignIn: (plugins: readonly PluginOption[]) => PluginRef[];
28
36
  //#endregion
@@ -1,6 +1,8 @@
1
1
  //#region src/plugins/plugin-settings.ts
2
2
  /** The platform's plugins, as plugin.json URLs separated by commas, loaded after the ones `options.plugins` lists. */
3
3
  const pluginsVariable = "PROTOBASE_PLUGINS";
4
+ /** The platform's plugins that load before sign-in, as `PROTOBASE_PLUGINS` lists them, after the ones it lists. */
5
+ const beforeSignInPluginsVariable = "PROTOBASE_PLUGINS_BEFORE_SIGN_IN";
4
6
  const checkedPluginUrl = (url, source) => {
5
7
  if (url.startsWith("/") && !url.startsWith("//")) return url;
6
8
  if (!URL.canParse(url)) throw new Error(`${source} names "${url}", which is no URL; use https://... or a path such as /plugins/name/plugin.json`);
@@ -8,10 +10,12 @@ const checkedPluginUrl = (url, source) => {
8
10
  if (protocol !== "https:" && protocol !== "http:") throw new Error(`${source} names "${url}", which must start with https:// or http://, not ${protocol}//`);
9
11
  return url;
10
12
  };
13
+ const listedIn = (env, variable) => (env[variable] ?? "").split(",").map((url) => url.trim()).filter((url) => url !== "").map((url) => checkedPluginUrl(url, variable));
11
14
  /**
12
15
  * The plugins the app loads: the `bundled` ones its bundle carries, then those `options.plugins` lists, then those
13
- * `PROTOBASE_PLUGINS` adds, each URL once. `false` turns off the last two; the bundle's are the app's own UI. A value
14
- * that is no URL stops the server at startup.
16
+ * `PROTOBASE_PLUGINS` and `PROTOBASE_PLUGINS_BEFORE_SIGN_IN` add, each URL once, where it is first listed. A URL either
17
+ * of the last two lists loads before sign-in. `false` turns off all but the bundle's, which are the app's own UI. A
18
+ * value that is no URL, or a plugin that loads before sign-in only for some roles, stops the server at startup.
15
19
  */
16
20
  const pluginOptions = (option, env, bundled = []) => {
17
21
  const own = bundled.map((url) => ({ url: checkedPluginUrl(url, "the bundle") }));
@@ -20,18 +24,29 @@ const pluginOptions = (option, env, bundled = []) => {
20
24
  ...plugin,
21
25
  url: checkedPluginUrl(plugin.url.trim(), "options.plugins")
22
26
  }));
23
- const listed = (env["PROTOBASE_PLUGINS"] ?? "").split(",").map((url) => url.trim()).filter((url) => url !== "").map((url) => ({ url: checkedPluginUrl(url, pluginsVariable) }));
27
+ const listed = listedIn(env, pluginsVariable).map((url) => ({ url }));
28
+ const early = new Set(listedIn(env, beforeSignInPluginsVariable));
24
29
  const all = [
25
30
  ...own,
26
31
  ...given,
27
- ...listed
32
+ ...listed,
33
+ ...[...early].map((url) => ({ url }))
28
34
  ];
29
- return all.filter((plugin, index) => all.findIndex((other) => other.url === plugin.url) === index);
35
+ const plugins = all.filter((plugin, index) => all.findIndex((other) => other.url === plugin.url) === index).map((plugin) => early.has(plugin.url) ? {
36
+ ...plugin,
37
+ beforeSignIn: true
38
+ } : plugin);
39
+ const limited = plugins.find((plugin) => plugin.beforeSignIn && plugin.roles);
40
+ if (limited) throw new Error(`The plugin ${limited.url} loads before sign-in and only for some roles; someone signed out has no role, so drop its roles or beforeSignIn`);
41
+ return plugins;
30
42
  };
31
- /** The plugins `/meta` names to a caller with `roles`: those for everyone, and those for one of their roles. */
32
- const pluginsFor = (plugins, roles) => plugins.filter((plugin) => !plugin.roles || plugin.roles.some((role) => roles.includes(role))).map(({ url, settings }) => ({
43
+ const refOf = ({ url, settings }) => ({
33
44
  url,
34
45
  ...settings && { settings }
35
- }));
46
+ });
47
+ /** The plugins `/meta` names to a caller with `roles`: those for everyone, and those for one of their roles. */
48
+ const pluginsFor = (plugins, roles) => plugins.filter((plugin) => !plugin.roles || plugin.roles.some((role) => roles.includes(role))).map(refOf);
49
+ /** The plugins the sign-in status names to everyone, for the app to load before anyone signs in. */
50
+ const pluginsBeforeSignIn = (plugins) => plugins.filter((plugin) => plugin.beforeSignIn).map(refOf);
36
51
  //#endregion
37
- export { pluginOptions, pluginsFor, pluginsVariable };
52
+ export { beforeSignInPluginsVariable, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
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": {
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@better-auth/passkey": "^1.7.7",
33
- "@protobase/layout": "0.19.0",
34
- "@protobase/query": "0.19.0",
35
- "@protobase/schema": "0.19.0",
33
+ "@protobase/layout": "0.19.1",
34
+ "@protobase/query": "0.19.1",
35
+ "@protobase/schema": "0.19.1",
36
36
  "@react-email/render": "^2.1.0",
37
37
  "@scalar/hono-api-reference": "^0.12.10",
38
38
  "@sentry/core": "^11.7.0",