-
Notifications
You must be signed in to change notification settings - Fork 138
Derive scope types and WHERE helpers from single source of truth #2159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@inkeep/agents-core": patch | ||
| --- | ||
|
|
||
| Derive scope config types and WHERE helpers from single source of truth (scope-definitions.ts) |
77 changes: 38 additions & 39 deletions
77
packages/agents-core/src/data-access/manage/scope-helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,47 +1,46 @@ | ||
| import { and, eq } from 'drizzle-orm'; | ||
| import type { | ||
| AgentScopeConfig, | ||
| ProjectScopeConfig, | ||
| SubAgentScopeConfig, | ||
| } from '../../types/utility'; | ||
| import { and, eq, type SQL } from 'drizzle-orm'; | ||
| import { | ||
| SCOPE_KEYS, | ||
| type ScopeConfig, | ||
| type ScopedTable, | ||
| type ScopeLevel, | ||
| } from '../../db/manage/scope-definitions'; | ||
|
|
||
| type TenantScopeConfig = { tenantId: string }; | ||
|
|
||
| type TenantScopedColumns = { tenantId: any }; | ||
| type ProjectScopedColumns = TenantScopedColumns & { projectId: any }; | ||
| type AgentScopedColumns = ProjectScopedColumns & { agentId: any }; | ||
| type SubAgentScopedColumns = AgentScopedColumns & { subAgentId: any }; | ||
| /** | ||
| * Build a WHERE clause that filters by all scope columns for the given level. | ||
| * | ||
| * The columns and config shape are both derived from `SCOPE_KEYS` in | ||
| * `scope-definitions.ts`, so they cannot drift apart. | ||
| */ | ||
| export function scopedWhere<L extends ScopeLevel>( | ||
| level: L, | ||
| table: ScopedTable<L>, | ||
| scopes: ScopeConfig<L> | ||
| ): SQL | undefined { | ||
| const keys = SCOPE_KEYS[level]; | ||
| const conditions = keys.map((key) => | ||
| eq((table as Record<string, any>)[key], (scopes as Record<string, string>)[key]) | ||
| ); | ||
| return conditions.length === 1 ? conditions[0] : and(...conditions); | ||
| } | ||
|
|
||
| export function tenantScopedWhere<T extends TenantScopedColumns>( | ||
| // Named wrappers for ergonomics and grep-ability. | ||
| export const tenantScopedWhere = <T extends ScopedTable<'tenant'>>( | ||
| table: T, | ||
| scopes: TenantScopeConfig | ||
| ) { | ||
| return eq(table.tenantId, scopes.tenantId); | ||
| } | ||
| scopes: ScopeConfig<'tenant'> | ||
| ) => scopedWhere('tenant', table, scopes); | ||
|
|
||
| export function projectScopedWhere<T extends ProjectScopedColumns>( | ||
| export const projectScopedWhere = <T extends ScopedTable<'project'>>( | ||
| table: T, | ||
| scopes: ProjectScopeConfig | ||
| ) { | ||
| return and(eq(table.tenantId, scopes.tenantId), eq(table.projectId, scopes.projectId)); | ||
| } | ||
| scopes: ScopeConfig<'project'> | ||
| ) => scopedWhere('project', table, scopes); | ||
|
|
||
| export function agentScopedWhere<T extends AgentScopedColumns>(table: T, scopes: AgentScopeConfig) { | ||
| return and( | ||
| eq(table.tenantId, scopes.tenantId), | ||
| eq(table.projectId, scopes.projectId), | ||
| eq(table.agentId, scopes.agentId) | ||
| ); | ||
| } | ||
| export const agentScopedWhere = <T extends ScopedTable<'agent'>>( | ||
| table: T, | ||
| scopes: ScopeConfig<'agent'> | ||
| ) => scopedWhere('agent', table, scopes); | ||
|
|
||
| export function subAgentScopedWhere<T extends SubAgentScopedColumns>( | ||
| export const subAgentScopedWhere = <T extends ScopedTable<'subAgent'>>( | ||
| table: T, | ||
| scopes: SubAgentScopeConfig | ||
| ) { | ||
| return and( | ||
| eq(table.tenantId, scopes.tenantId), | ||
| eq(table.projectId, scopes.projectId), | ||
| eq(table.agentId, scopes.agentId), | ||
| eq(table.subAgentId, scopes.subAgentId) | ||
| ); | ||
| } | ||
| scopes: ScopeConfig<'subAgent'> | ||
| ) => scopedWhere('subAgent', table, scopes); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * Single source of truth for hierarchical scope column definitions. | ||
| * | ||
| * Everything else — TypeScript config types, WHERE clause helpers, and | ||
| * compile-time table constraints — is derived from these key arrays. | ||
| * | ||
| * The schema column helpers (tenantScoped, projectScoped, etc.) in | ||
| * manage-schema.ts define the Drizzle columns using the same key names. | ||
| * If a key is added or removed here, the schema columns and all | ||
| * downstream consumers (types, query helpers) must be updated together — | ||
| * but now they'll fail at compile time instead of silently drifting. | ||
| */ | ||
| export const SCOPE_KEYS = { | ||
| tenant: ['tenantId'] as const, | ||
| project: ['tenantId', 'projectId'] as const, | ||
| agent: ['tenantId', 'projectId', 'agentId'] as const, | ||
| subAgent: ['tenantId', 'projectId', 'agentId', 'subAgentId'] as const, | ||
| } as const; | ||
|
|
||
| export type ScopeLevel = keyof typeof SCOPE_KEYS; | ||
|
|
||
| export type ScopeKeysOf<L extends ScopeLevel> = (typeof SCOPE_KEYS)[L][number]; | ||
|
|
||
| /** A table that has the columns required for a given scope level. */ | ||
| export type ScopedTable<L extends ScopeLevel> = { | ||
| [K in ScopeKeysOf<L>]: any; | ||
| }; | ||
|
|
||
| /** The parameter object shape required to filter by a given scope level. */ | ||
| export type ScopeConfig<L extends ScopeLevel> = { | ||
| [K in ScopeKeysOf<L>]: string; | ||
| }; | ||
|
|
||
| // Named type aliases for convenience and backwards compatibility. | ||
| export type TenantScopeConfig = ScopeConfig<'tenant'>; | ||
| export type ProjectScopeConfig = ScopeConfig<'project'>; | ||
| export type AgentScopeConfig = ScopeConfig<'agent'>; | ||
| export type SubAgentScopeConfig = ScopeConfig<'subAgent'>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 Consider: Export
TenantScopeConfigfor completenessIssue: The new
scope-definitions.tsexportsTenantScopeConfig, but it's not re-exported here with the other scope config types.Why: Minor asymmetry —
TenantScopeConfigwas only defined locally inscope-helpers.tsbefore, so this isn't a regression. However, consumers who need it must import from the deeper path while other scope configs come from this barrel.Fix:
Refs:
TenantScopeConfigexport