@protobase/server 0.46.5 → 0.46.7
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/aggregate-service.js +10 -4
- package/dist/better-auth/authenticator.d.ts +2 -1
- package/dist/better-auth/authenticator.js +7 -2
- package/dist/better-auth/organizations/session-memberships.js +29 -0
- package/dist/index.d.ts +2 -2
- package/dist/list-photos.js +3 -2
- package/dist/list-service.js +5 -3
- package/dist/map-service.js +3 -2
- package/dist/meta.js +2 -1
- package/dist/nav-tenant.js +42 -0
- package/dist/package.js +1 -1
- package/dist/read-scope.js +65 -0
- package/dist/related-labels.js +3 -2
- package/dist/request-access.js +3 -3
- package/dist/row-permissions.js +13 -5
- package/dist/types.d.ts +11 -0
- package/dist/view-access.js +1 -0
- package/package.json +4 -4
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { badRequest } from "./problem.js";
|
|
2
2
|
import { checkedFilter } from "./filter-input.js";
|
|
3
|
-
import {
|
|
3
|
+
import { requestAccess, requireOperation } from "./request-access.js";
|
|
4
4
|
import { readTransaction } from "./transactions.js";
|
|
5
5
|
import { columnId, filterCondition, scopeConditions, tableId, whereClause } from "./sql-parts.js";
|
|
6
6
|
import { parseParams } from "./params.js";
|
|
7
|
+
import { readScope } from "./read-scope.js";
|
|
7
8
|
import { labelRequests, relatedLabels } from "./related-labels.js";
|
|
8
9
|
import { rangeEnd, rangeStart } from "./range-ends.js";
|
|
9
10
|
import { z } from "zod";
|
|
10
11
|
import { sql } from "kysely";
|
|
11
|
-
import { facetCounts, histogram, series, withoutField } from "@protobase/query";
|
|
12
|
+
import { facetCounts, histogram, series, tenantCondition, withoutField } from "@protobase/query";
|
|
12
13
|
//#region src/aggregate-service.ts
|
|
13
14
|
const field = z.string().min(1);
|
|
14
15
|
const filter = z.string().optional();
|
|
@@ -64,7 +65,7 @@ const scoped = async (deps, entry, session, source) => {
|
|
|
64
65
|
access,
|
|
65
66
|
model: access.model,
|
|
66
67
|
filter: checkedFilter(access.model, source),
|
|
67
|
-
scope: readScope(access)
|
|
68
|
+
scope: await readScope(access)
|
|
68
69
|
};
|
|
69
70
|
};
|
|
70
71
|
/**
|
|
@@ -108,7 +109,12 @@ const facets = async (deps, entry, session, input) => {
|
|
|
108
109
|
};
|
|
109
110
|
const earliest = async (deps, access, fieldName, scope) => {
|
|
110
111
|
const target = access.model.fields[fieldName];
|
|
111
|
-
const
|
|
112
|
+
const across = scope.tenantValues ? [tenantCondition(access.full, scope)] : [];
|
|
113
|
+
const conditions = [
|
|
114
|
+
...scopeConditions(access.full, scope.tenantValue),
|
|
115
|
+
...across,
|
|
116
|
+
...filterCondition(access.full, scope.rowFilter)
|
|
117
|
+
];
|
|
112
118
|
return (await readTransaction(deps.db, deps.statementTimeoutMs, (trx) => sql`select min(${columnId(target)})::text as lowest from ${tableId(access.full)} as t${whereClause(conditions)}`.execute(trx))).rows[0]?.lowest ?? void 0;
|
|
113
119
|
};
|
|
114
120
|
const bound = (model, fieldName, value) => model.fields[fieldName]?.type === "date" ? value.slice(0, 10) : value;
|
|
@@ -49,7 +49,8 @@ export declare const betterAuthTokens: (options: Pick<BetterAuthAuthenticatorOpt
|
|
|
49
49
|
* organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
|
|
50
50
|
* role, is left out. With `auth`, every request also reads the store to check that the token still stands
|
|
51
51
|
* (`checkTokenStanding`), so a ban, a lowered role, a deleted user or a sign-out ends it at once; with `jwksUrl` alone
|
|
52
|
-
* a token works until it expires. With `
|
|
52
|
+
* a token works until it expires. With organizations and `auth`, the session's `memberships` reads the person's
|
|
53
|
+
* organizations from the store when a list across them asks (`sessionMemberships`). With `apps`, the tokens of those apps are admitted too, with the roles each gives
|
|
53
54
|
* its users here.
|
|
54
55
|
*/
|
|
55
56
|
export declare const betterAuthAuthenticator: (options: BetterAuthAuthenticatorOptions) => BetterAuthAuthenticator;
|
|
@@ -3,6 +3,7 @@ import { noGlobalRole } from "./organizations/role-definitions.js";
|
|
|
3
3
|
import { cachedKeys } from "./jwks-cache.js";
|
|
4
4
|
import { withTrustedApps } from "./app-tokens.js";
|
|
5
5
|
import { checkMcpGrant } from "./mcp/grant-standing.js";
|
|
6
|
+
import { sessionMemberships } from "./organizations/session-memberships.js";
|
|
6
7
|
import { checkTokenStanding, checkUserStanding, tokenClaims } from "./token-standing.js";
|
|
7
8
|
import { createDpopReplayStore, createInMemoryDpopReplayStore } from "better-auth/oauth2";
|
|
8
9
|
//#region src/better-auth/authenticator.ts
|
|
@@ -23,7 +24,8 @@ const betterAuthTokens = (options) => {
|
|
|
23
24
|
* organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
|
|
24
25
|
* role, is left out. With `auth`, every request also reads the store to check that the token still stands
|
|
25
26
|
* (`checkTokenStanding`), so a ban, a lowered role, a deleted user or a sign-out ends it at once; with `jwksUrl` alone
|
|
26
|
-
* a token works until it expires. With `
|
|
27
|
+
* a token works until it expires. With organizations and `auth`, the session's `memberships` reads the person's
|
|
28
|
+
* organizations from the store when a list across them asks (`sessionMemberships`). With `apps`, the tokens of those apps are admitted too, with the roles each gives
|
|
27
29
|
* its users here.
|
|
28
30
|
*/
|
|
29
31
|
const betterAuthAuthenticator = (options) => {
|
|
@@ -58,10 +60,13 @@ const betterAuthAuthenticator = (options) => {
|
|
|
58
60
|
...session.user,
|
|
59
61
|
roles: session.user.roles.filter((role) => role !== noGlobalRole)
|
|
60
62
|
};
|
|
63
|
+
let memberships;
|
|
64
|
+
const across = auth && !verifyOptions.oauthClient && { memberships: async () => memberships ??= auth.$context.then((context) => sessionMemberships(context, String(user.id), user.roles)) };
|
|
61
65
|
return {
|
|
62
66
|
...session,
|
|
63
67
|
user,
|
|
64
|
-
...session.tenant !== void 0 && { organization: { id: session.tenant } }
|
|
68
|
+
...session.tenant !== void 0 && { organization: { id: session.tenant } },
|
|
69
|
+
...across
|
|
65
70
|
};
|
|
66
71
|
};
|
|
67
72
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { effectiveRoles, globalRoles } from "./app-roles.js";
|
|
2
|
+
import { membershipsOf } from "./store.js";
|
|
3
|
+
import { resolveActiveRole } from "../roles/active-role.js";
|
|
4
|
+
//#region src/better-auth/organizations/session-memberships.ts
|
|
5
|
+
/**
|
|
6
|
+
* The organizations someone is a member of, read from the store, each with the role they would act with there after a
|
|
7
|
+
* switch: the session's active role (`active`) when they hold it there, globally or as the member, else their first
|
|
8
|
+
* role there, else none. Organizations they only enter with a global role are not memberships, so they are left out.
|
|
9
|
+
*/
|
|
10
|
+
const sessionMemberships = async ({ adapter }, userId, active) => {
|
|
11
|
+
const user = await adapter.findOne({
|
|
12
|
+
model: "user",
|
|
13
|
+
where: [{
|
|
14
|
+
field: "id",
|
|
15
|
+
value: userId
|
|
16
|
+
}]
|
|
17
|
+
});
|
|
18
|
+
if (!user) return [];
|
|
19
|
+
const global = globalRoles(user.role);
|
|
20
|
+
return (await membershipsOf(adapter, userId)).map((member) => {
|
|
21
|
+
const role = resolveActiveRole(effectiveRoles(global, member.appRoles), active);
|
|
22
|
+
return {
|
|
23
|
+
tenant: member.organizationId,
|
|
24
|
+
roles: role === void 0 ? [] : [role]
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { sessionMemberships };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { HistoryPosition, NewNotification, StoredNotification, historyIdPattern,
|
|
|
18
18
|
import { PushMessage, PushMessageError, Urgency, WebPushFields, maxTtlSeconds, pushMessage, urgencies } from "./notifications/message.js";
|
|
19
19
|
import { NotificationRecipients, NotificationSendError, SendNotification, maxMessageBytes, maxRecipients } from "./notifications/send-input.js";
|
|
20
20
|
import { EnqueueOptions, JobContext, JobDefinition, JobOptions } from "./jobs/job-types.js";
|
|
21
|
-
import { Admin, AuditEvent, AuditQueue, Authenticator, PipelineHook, ScanGuardMode, Session, SessionOrganization, TenantValue, User, ViewSource, WriteEvent, WriteOperation } from "./types.js";
|
|
21
|
+
import { Admin, AuditEvent, AuditQueue, Authenticator, Membership, PipelineHook, ScanGuardMode, Session, SessionOrganization, TenantValue, User, ViewSource, WriteEvent, WriteOperation } from "./types.js";
|
|
22
22
|
import { CommentUsers } from "./comments/comment-users.js";
|
|
23
23
|
import { HistoryOptions } from "./history/options.js";
|
|
24
24
|
import { WebhooksOptions } from "./webhooks/table/options.js";
|
|
@@ -161,4 +161,4 @@ import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.
|
|
|
161
161
|
import { MobileRelease, ReleaseFile, ServedReleaseFile, composeRelease, releaseManifest } from "./mobile-release/release.js";
|
|
162
162
|
import { MobileReleaseAppInput, OfferedRelease, mobileReleaseApp } from "./mobile-release/release-app.js";
|
|
163
163
|
import { mobileSigningKeyVariable, readSigningKey } from "./mobile-release/signing-key.js";
|
|
164
|
-
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, 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 FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, MissingCredentials, type MobileConfig, type MobileRelease, type MobileReleaseAppInput, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OfferedRelease, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type ReleaseFile, 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 SendNotification, type ServedReleaseFile, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
|
|
164
|
+
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type Caller, type CallerOptions, type CapturedRow, type CascadingKey, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldStatus, 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 FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, type MobileRelease, type MobileReleaseAppInput, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OfferedRelease, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordKeyInput, type RecordPage, type ReleaseFile, 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 SendNotification, type ServedReleaseFile, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appPool, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthAuthenticator, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, composeRelease, computedMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, deviceIdParam, deviceStore, devicesPath, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, localFiles, longQueryPool, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpRoutes, mcpServer, mcpToolContext, mcpTools, mergeConfig, migrationCommand, mobileReleaseApp, mobileSigningKeyVariable, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notifications, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readSigningKey, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, recordOf, releaseManifest, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
|
package/dist/list-photos.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { badRequest } from "./problem.js";
|
|
2
2
|
import { checkedSort } from "./filter-input.js";
|
|
3
|
-
import {
|
|
3
|
+
import { requestAccess } from "./request-access.js";
|
|
4
4
|
import { readTransaction } from "./transactions.js";
|
|
5
5
|
import { exposeRow } from "./rows.js";
|
|
6
6
|
import { presentFiles } from "./files/present.js";
|
|
7
|
+
import { readScope } from "./read-scope.js";
|
|
7
8
|
import { cardPhotosErrors } from "./view-card-photos.js";
|
|
8
9
|
import { cardPhotoLimit } from "@protobase/schema/internal";
|
|
9
10
|
import { checkFilter, createWhere } from "@protobase/schema";
|
|
@@ -66,7 +67,7 @@ const recordPhotos = async (deps, request, model, rows) => {
|
|
|
66
67
|
const sort = checkedSort(access.model, photos.sort);
|
|
67
68
|
const filter = checkFilter(access.model, where$1.in(photos.field, keys));
|
|
68
69
|
if (!filter.ok) throw new Error(`Keys of "${model.name}" do not make a filter of "${photos.resource}": ${filter.errors[0].message}`);
|
|
69
|
-
const scope = readScope(access);
|
|
70
|
+
const scope = await readScope(access);
|
|
70
71
|
const warning = await deps.guard(access.entry, {
|
|
71
72
|
filter: filter.filter,
|
|
72
73
|
...sort && { sort }
|
package/dist/list-service.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { checkedFilter, checkedSort } from "./filter-input.js";
|
|
2
|
-
import {
|
|
2
|
+
import { requestAccess, requireOperation } from "./request-access.js";
|
|
3
3
|
import { readTransaction } from "./transactions.js";
|
|
4
4
|
import { etagAlias } from "./sql-parts.js";
|
|
5
5
|
import { etagOf } from "./etag.js";
|
|
6
6
|
import { exposeRow, pickFields } from "./rows.js";
|
|
7
7
|
import { presentFiles } from "./files/present.js";
|
|
8
8
|
import { deletedRows, listParams, pageSizeOf, parseParams, seekParams, selectedFields } from "./params.js";
|
|
9
|
+
import { readScope } from "./read-scope.js";
|
|
9
10
|
import { rowPermissions } from "./row-permissions.js";
|
|
10
11
|
import { labelRequests, relatedLabels } from "./related-labels.js";
|
|
11
12
|
import { photosRequest, recordPhotos } from "./list-photos.js";
|
|
@@ -38,7 +39,7 @@ const listRecords = async (deps, entry, session, input, maxFilterLength, jump =
|
|
|
38
39
|
const labels = await labelRequests(deps, session, model, columns, params.labels);
|
|
39
40
|
const photos = await photosRequest(deps, session, model, params.photos, params.photos_order_by);
|
|
40
41
|
const limit = pageSizeOf(params.page_size);
|
|
41
|
-
const scope = readScope(access, full);
|
|
42
|
+
const scope = await readScope(access, full);
|
|
42
43
|
const etagField = entry.etag && !model.fields[entry.etag.field.name] ? full.fields[entry.etag.field.name] : void 0;
|
|
43
44
|
const queryModel = etagField ? {
|
|
44
45
|
...model,
|
|
@@ -48,7 +49,8 @@ const listRecords = async (deps, entry, session, input, maxFilterLength, jump =
|
|
|
48
49
|
}
|
|
49
50
|
} : model;
|
|
50
51
|
const withKey = (photos !== void 0 || columns?.some((name) => model.fields[name].type === "file")) && columns ? [.../* @__PURE__ */ new Set([...columns, ...entry.model.primaryKey.filter((name) => model.fields[name])])] : columns;
|
|
51
|
-
const
|
|
52
|
+
const across = model.tenantAcross && model.tenant !== void 0 ? [model.tenant] : [];
|
|
53
|
+
const read = entry.etag && withKey ? [.../* @__PURE__ */ new Set([...withKey, ...across])] : Object.keys(model.fields);
|
|
52
54
|
const shape = {
|
|
53
55
|
...filter && { filter },
|
|
54
56
|
...sort && { sort },
|
package/dist/map-service.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { checkedFilter } from "./filter-input.js";
|
|
2
|
-
import {
|
|
2
|
+
import { requestAccess, requireOperation } from "./request-access.js";
|
|
3
3
|
import { readTransaction } from "./transactions.js";
|
|
4
4
|
import { parseParams, selectedFields } from "./params.js";
|
|
5
|
+
import { readScope } from "./read-scope.js";
|
|
5
6
|
import { labelRequests } from "./related-labels.js";
|
|
6
7
|
import { photosRequest } from "./list-photos.js";
|
|
7
8
|
import { listRecords } from "./list-service.js";
|
|
@@ -33,7 +34,7 @@ const mapRecords = async (deps, entry, session, input) => {
|
|
|
33
34
|
};
|
|
34
35
|
checkMapPosition(access.model, position);
|
|
35
36
|
const filter = checkedFilter(access.model, params.filter);
|
|
36
|
-
const scope = readScope(access);
|
|
37
|
+
const scope = await readScope(access);
|
|
37
38
|
const found = await readTransaction(deps.db, deps.statementTimeoutMs, (trx) => countUpTo(trx, access.model, 201, filter, scope));
|
|
38
39
|
if (found <= 200 || params.zoom >= 16) {
|
|
39
40
|
const { latitude: _latitude, longitude: _longitude, zoom: _zoom, ...asked } = params;
|
package/dist/meta.js
CHANGED
|
@@ -5,6 +5,7 @@ import { pruneView } from "./view-access.js";
|
|
|
5
5
|
import { activeRole } from "./active-role.js";
|
|
6
6
|
import { pluginsFor } from "./plugins/plugin-settings.js";
|
|
7
7
|
import { metaModel } from "./meta-model.js";
|
|
8
|
+
import { withCurrentTenant } from "./nav-tenant.js";
|
|
8
9
|
import { protobaseVersion } from "./protobase-version.js";
|
|
9
10
|
import { seesRuntime } from "./runtime/runtime-settings.js";
|
|
10
11
|
import { sourcesFor } from "./sources/source-settings.js";
|
|
@@ -46,7 +47,7 @@ const createMeta = (deps, appName, appIcon) => {
|
|
|
46
47
|
}] : [];
|
|
47
48
|
});
|
|
48
49
|
const createPages = new Set(picked.filter(({ view }) => view.create).map(({ view }) => view.resource));
|
|
49
|
-
const views = picked.map(({ access, view }) => pruneView(view, access.readable, listed, prompted, createPages, new Set(writeOnlyFields(access))));
|
|
50
|
+
const views = picked.map(({ access, view }) => withCurrentTenant(pruneView(view, access.readable, listed, prompted, createPages, new Set(writeOnlyFields(access))), access.model.tenant, session.tenant));
|
|
50
51
|
const callerAccess = {
|
|
51
52
|
createPages,
|
|
52
53
|
models: listed,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseFilter, printFilter, where } from "@protobase/schema";
|
|
2
|
+
//#region src/nav-tenant.ts
|
|
3
|
+
const narrowed = (filter, field, tenant) => {
|
|
4
|
+
const term = where.eq(field, tenant);
|
|
5
|
+
const parsed = filter?.trim() ? parseFilter(filter) : void 0;
|
|
6
|
+
if (parsed && !parsed.ok) throw new Error(`The filter "${filter}" does not parse: ${parsed.errors[0].message}`);
|
|
7
|
+
return printFilter(parsed?.ast ? where.and(parsed.ast, term) : term);
|
|
8
|
+
};
|
|
9
|
+
const onTenant = (entry, field, tenant) => {
|
|
10
|
+
if (entry.nav?.tenant !== "current" || !entry.list) return entry;
|
|
11
|
+
const recent = entry.nav.recent && {
|
|
12
|
+
...entry.nav.recent,
|
|
13
|
+
filter: narrowed(entry.nav.recent.filter, field, tenant)
|
|
14
|
+
};
|
|
15
|
+
const nav = {
|
|
16
|
+
...entry.nav,
|
|
17
|
+
...recent && { recent }
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
...entry,
|
|
21
|
+
nav,
|
|
22
|
+
list: {
|
|
23
|
+
...entry.list,
|
|
24
|
+
filter: narrowed(entry.list.filter, field, tenant)
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The view as `/meta` gives it to a caller in `tenant`: each entry whose `nav.tenant` is `current`, the view's own and
|
|
30
|
+
* its named lists', opens its list on that tenant, with `tenantField = "<tenant>"` added to the list's opening filter
|
|
31
|
+
* and its recent records' filter. Unchanged for a caller who cannot see the tenant field, or works in no tenant.
|
|
32
|
+
*/
|
|
33
|
+
const withCurrentTenant = (view, tenantField, tenant) => {
|
|
34
|
+
if (tenantField === void 0 || tenant === void 0) return view;
|
|
35
|
+
const own = onTenant(view, tenantField, tenant);
|
|
36
|
+
return own.lists ? {
|
|
37
|
+
...own,
|
|
38
|
+
lists: own.lists.map((list) => onTenant(list, tenantField, tenant))
|
|
39
|
+
} : own;
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { withCurrentTenant };
|
package/dist/package.js
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { andFilters, ownScope, requestAccess, trusted } from "./request-access.js";
|
|
2
|
+
import { checkFilter, where } from "@protobase/schema";
|
|
3
|
+
//#region src/read-scope.ts
|
|
4
|
+
const sameTenant = (a, b) => String(a) === String(b);
|
|
5
|
+
const memberSession = (session, membership) => ({
|
|
6
|
+
...session,
|
|
7
|
+
user: {
|
|
8
|
+
...session.user,
|
|
9
|
+
roles: membership.roles
|
|
10
|
+
},
|
|
11
|
+
tenant: membership.tenant,
|
|
12
|
+
...session.organization && { organization: { id: membership.tenant } }
|
|
13
|
+
});
|
|
14
|
+
const listsAsMuch = (own, other) => other.resolved.operations.list && Object.keys(own.model.fields).every((name) => Object.hasOwn(other.model.fields, name));
|
|
15
|
+
const otherTenants = async (access) => {
|
|
16
|
+
const { deps, entry, session } = access;
|
|
17
|
+
const own = session.tenant;
|
|
18
|
+
if (!access.model.tenantAcross || own === void 0 || !session.memberships) return [];
|
|
19
|
+
const memberships = new Map((await session.memberships()).filter((membership) => !sameTenant(membership.tenant, own)).map((membership) => [String(membership.tenant), membership]));
|
|
20
|
+
return (await Promise.all([...memberships.values()].map(async (membership) => {
|
|
21
|
+
const other = await requestAccess(deps, entry, memberSession(session, membership));
|
|
22
|
+
return listsAsMuch(access, other) ? [{
|
|
23
|
+
tenant: membership.tenant,
|
|
24
|
+
rowFilter: other.resolved.rowFilter.read
|
|
25
|
+
}] : [];
|
|
26
|
+
}))).flat();
|
|
27
|
+
};
|
|
28
|
+
const acrossFilter = (full, reads) => {
|
|
29
|
+
if (reads.every((read) => read.rowFilter === void 0)) return void 0;
|
|
30
|
+
const tenantIs = (value) => {
|
|
31
|
+
const checked = checkFilter(trusted(full), where.eq(full.tenant, value));
|
|
32
|
+
if (!checked.ok) throw new Error(`The tenant ${value} of "${full.name}" does not make a filter: ${checked.errors[0].message}`);
|
|
33
|
+
return checked.filter;
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
kind: "or",
|
|
37
|
+
args: reads.map((read) => andFilters([tenantIs(read.tenant), read.rowFilter])),
|
|
38
|
+
span: {
|
|
39
|
+
start: 0,
|
|
40
|
+
end: 0
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The query-layer scope of a list-style read (a list, its counts, facets, charts and map, and the labels and photos it
|
|
46
|
+
* brings): the caller's own tenant, or for a resource whose lists read across tenants (`tenantAcross`) also every other
|
|
47
|
+
* tenant the person is a member of, each with the row filter of their access there.
|
|
48
|
+
*/
|
|
49
|
+
const readScope = async (access, fullModel = access.full) => {
|
|
50
|
+
const own = ownScope(access, fullModel);
|
|
51
|
+
const others = await otherTenants(access);
|
|
52
|
+
if (others.length === 0) return own;
|
|
53
|
+
const reads = [{
|
|
54
|
+
tenant: own.tenantValue,
|
|
55
|
+
rowFilter: own.rowFilter
|
|
56
|
+
}, ...others];
|
|
57
|
+
const rowFilter = acrossFilter(fullModel, reads);
|
|
58
|
+
return {
|
|
59
|
+
tenantValues: reads.map((read) => read.tenant),
|
|
60
|
+
...rowFilter && { rowFilter },
|
|
61
|
+
fullModel
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { readScope };
|
package/dist/related-labels.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { badRequest } from "./problem.js";
|
|
2
|
-
import {
|
|
2
|
+
import { requestAccess } from "./request-access.js";
|
|
3
3
|
import { exposeRow } from "./rows.js";
|
|
4
|
+
import { readScope } from "./read-scope.js";
|
|
4
5
|
import { checkFilter, createWhere } from "@protobase/schema";
|
|
5
6
|
import { listQuery } from "@protobase/query";
|
|
6
7
|
//#region src/related-labels.ts
|
|
@@ -61,7 +62,7 @@ const namesOf = async (trx, { field, access }, keys) => {
|
|
|
61
62
|
filter: filter.filter,
|
|
62
63
|
columns: [key, field],
|
|
63
64
|
limit: keys.length
|
|
64
|
-
}, readScope(access))).rows.map((row) => exposeRow(model, row)).filter((row) => row[field] !== null && row[field] !== void 0);
|
|
65
|
+
}, await readScope(access))).rows.map((row) => exposeRow(model, row)).filter((row) => row[field] !== null && row[field] !== void 0);
|
|
65
66
|
return Object.fromEntries(named.map((row) => [String(row[key]), String(row[field])]));
|
|
66
67
|
};
|
|
67
68
|
/** The names of the records the rows' relations point at, one query per requested relation. */
|
package/dist/request-access.js
CHANGED
|
@@ -125,8 +125,8 @@ const andFilters = (filters) => {
|
|
|
125
125
|
};
|
|
126
126
|
/** The row filter that narrows which rows an operation may target: the read filter, plus the operation's own. */
|
|
127
127
|
const targetFilter = (access, operation) => operation === "read" ? access.resolved.rowFilter.read : andFilters([access.resolved.rowFilter.read, access.resolved.rowFilter[operation]]);
|
|
128
|
-
/** The query-layer scope
|
|
129
|
-
const
|
|
128
|
+
/** The query-layer scope of the caller's own tenant: it, the access row filter against the full model, soft delete from the full model. */
|
|
129
|
+
const ownScope = (access, fullModel = access.full) => {
|
|
130
130
|
const tenant = tenantScope(access.entry, access.session);
|
|
131
131
|
const rowFilter = access.resolved.rowFilter.read;
|
|
132
132
|
return {
|
|
@@ -173,4 +173,4 @@ const recordDecision = async (access, operation, record, input) => {
|
|
|
173
173
|
return asFilter(access, result, operation);
|
|
174
174
|
};
|
|
175
175
|
//#endregion
|
|
176
|
-
export { andFilters,
|
|
176
|
+
export { andFilters, ownScope, recordDecision, requestAccess, requireOperation, targetFilter, trusted, visibleFields, writableFields, writeOnlyFields };
|
package/dist/row-permissions.js
CHANGED
|
@@ -20,17 +20,25 @@ const matching = async (trx, access, rows, filter) => {
|
|
|
20
20
|
const found = await sql`select ${sql.join(keyFields.map(aliasedColumn))} from ${tableId(full)} as t${whereClause(conditions)}`.execute(trx);
|
|
21
21
|
return new Set(found.rows.map((row) => keyOf(access, row)));
|
|
22
22
|
};
|
|
23
|
+
const elsewhere = (access, row) => {
|
|
24
|
+
const tenant = access.full.tenant;
|
|
25
|
+
return access.model.tenantAcross !== void 0 && tenant !== void 0 && String(row[tenant]) !== String(access.session.tenant);
|
|
26
|
+
};
|
|
23
27
|
/**
|
|
24
|
-
* What the caller may do with each of these (exposed) rows: the
|
|
25
|
-
* operation's row filter (checked in the database), a record-level rule must pass
|
|
26
|
-
* (`.parent()`) must allow writing it. This only informs the UI; every write is
|
|
28
|
+
* What the caller may do with each of these (exposed) rows: the row must be of the caller's tenant, the operation must
|
|
29
|
+
* be allowed, the row must match the operation's row filter (checked in the database), a record-level rule must pass
|
|
30
|
+
* for the stored record, and its parents (`.parent()`) must allow writing it. This only informs the UI; every write is
|
|
31
|
+
* checked again by the write pipeline.
|
|
27
32
|
*/
|
|
28
33
|
const rowPermissions = async (trx, access, rows) => {
|
|
29
34
|
const { resolved, entry, session } = access;
|
|
30
|
-
const result = rows.map(() => ({
|
|
35
|
+
const result = rows.map((row) => elsewhere(access, row) ? {
|
|
36
|
+
update: false,
|
|
37
|
+
delete: false
|
|
38
|
+
} : {
|
|
31
39
|
update: true,
|
|
32
40
|
delete: true
|
|
33
|
-
})
|
|
41
|
+
});
|
|
34
42
|
if (rows.length === 0) return result;
|
|
35
43
|
for (const operation of ["update", "delete"]) {
|
|
36
44
|
if (!resolved.operations[operation]) {
|
package/dist/types.d.ts
CHANGED
|
@@ -13,11 +13,22 @@ export type User = {
|
|
|
13
13
|
export type SessionOrganization = {
|
|
14
14
|
id: TenantValue;
|
|
15
15
|
};
|
|
16
|
+
/** A tenant the person is a member of, with the roles they act with there. */
|
|
17
|
+
export type Membership = {
|
|
18
|
+
tenant: TenantValue;
|
|
19
|
+
roles: string[];
|
|
20
|
+
};
|
|
16
21
|
/** `anonymous` marks a request without a token, served as the project's `anonymous` role. */
|
|
17
22
|
export type Session = {
|
|
18
23
|
user: User;
|
|
19
24
|
tenant?: TenantValue;
|
|
20
25
|
organization?: SessionOrganization;
|
|
26
|
+
/**
|
|
27
|
+
* The tenants the person is a member of, each with the roles they act with there, read when a list of a resource
|
|
28
|
+
* with `.tenant(pick, { across: 'memberships' })` needs them. An authenticator sets it only when it knows them; without
|
|
29
|
+
* it, such lists read `tenant` alone. It must never answer a tenant the person is not a member of.
|
|
30
|
+
*/
|
|
31
|
+
memberships?: () => Promise<Membership[]>;
|
|
21
32
|
anonymous?: true;
|
|
22
33
|
/** The outside app acting as the user, such as an MCP client: its OAuth client id and the scopes its token carries. */
|
|
23
34
|
client?: {
|
package/dist/view-access.js
CHANGED
|
@@ -308,6 +308,7 @@ const viewShape = (resource, model, models, prompted, createPages, writeOnly) =>
|
|
|
308
308
|
hidden: shapes.data,
|
|
309
309
|
group: shapes.data,
|
|
310
310
|
order: shapes.data,
|
|
311
|
+
tenant: shapes.data,
|
|
311
312
|
recent: shapes.map((recent) => navRecentErrors(recent, model).length === 0 ? recent : void 0)
|
|
312
313
|
});
|
|
313
314
|
const chart = shapes.map((chart) => chartFields(chart).every((name) => Object.hasOwn(model.fields, name)) ? chart : void 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.7",
|
|
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": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"@better-auth/passkey": "^1.7.12",
|
|
37
37
|
"@better-auth/sso": "^1.7.12",
|
|
38
38
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
39
|
-
"@protobase/layout": "0.46.
|
|
40
|
-
"@protobase/query": "0.46.
|
|
41
|
-
"@protobase/schema": "0.46.
|
|
39
|
+
"@protobase/layout": "0.46.7",
|
|
40
|
+
"@protobase/query": "0.46.7",
|
|
41
|
+
"@protobase/schema": "0.46.7",
|
|
42
42
|
"@react-email/render": "^2.1.0",
|
|
43
43
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
44
44
|
"@sentry/core": "^11.7.0",
|