@checkstack/command-backend 0.0.4 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # @checkstack/command-backend
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @checkstack/backend-api@0.3.1
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 9faec1f: # Unified AccessRule Terminology Refactoring
14
+
15
+ This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
16
+
17
+ ## Changes
18
+
19
+ ### Core Infrastructure (`@checkstack/common`)
20
+
21
+ - Introduced `AccessRule` interface as the primary access control type
22
+ - Added `accessPair()` helper for creating read/manage access rule pairs
23
+ - Added `access()` builder for individual access rules
24
+ - Replaced `Permission` type with `AccessRule` throughout
25
+
26
+ ### API Changes
27
+
28
+ - `env.registerPermissions()` → `env.registerAccessRules()`
29
+ - `meta.permissions` → `meta.access` in RPC contracts
30
+ - `usePermission()` → `useAccess()` in frontend hooks
31
+ - Route `permission:` field → `accessRule:` field
32
+
33
+ ### UI Changes
34
+
35
+ - "Roles & Permissions" tab → "Roles & Access Rules"
36
+ - "You don't have permission..." → "You don't have access..."
37
+ - All permission-related UI text updated
38
+
39
+ ### Documentation & Templates
40
+
41
+ - Updated 18 documentation files with AccessRule terminology
42
+ - Updated 7 scaffolding templates with `accessPair()` pattern
43
+ - All code examples use new AccessRule API
44
+
45
+ ## Migration Guide
46
+
47
+ ### Backend Plugins
48
+
49
+ ```diff
50
+ - import { permissionList } from "./permissions";
51
+ - env.registerPermissions(permissionList);
52
+ + import { accessRules } from "./access";
53
+ + env.registerAccessRules(accessRules);
54
+ ```
55
+
56
+ ### RPC Contracts
57
+
58
+ ```diff
59
+ - .meta({ userType: "user", permissions: [permissions.read.id] })
60
+ + .meta({ userType: "user", access: [access.read] })
61
+ ```
62
+
63
+ ### Frontend Hooks
64
+
65
+ ```diff
66
+ - const canRead = accessApi.usePermission(permissions.read.id);
67
+ + const canRead = accessApi.useAccess(access.read);
68
+ ```
69
+
70
+ ### Routes
71
+
72
+ ```diff
73
+ - permission: permissions.entityRead.id,
74
+ + accessRule: access.read,
75
+ ```
76
+
77
+ ### Patch Changes
78
+
79
+ - Updated dependencies [9faec1f]
80
+ - Updated dependencies [827b286]
81
+ - Updated dependencies [f533141]
82
+ - Updated dependencies [aa4a8ab]
83
+ - @checkstack/backend-api@0.3.0
84
+ - @checkstack/command-common@0.1.0
85
+ - @checkstack/common@0.2.0
86
+
3
87
  ## 0.0.4
4
88
 
5
89
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/command-backend",
3
- "version": "0.0.4",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
package/src/registry.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { SearchResult } from "@checkstack/command-common";
2
2
  import {
3
- qualifyPermissionId,
3
+ qualifyAccessRuleId,
4
4
  type PluginMetadata,
5
- type Permission,
5
+ type AccessRule,
6
6
  type LucideIconName,
7
7
  } from "@checkstack/common";
8
8
 
@@ -14,13 +14,13 @@ import {
14
14
  * Context provided to search providers during search operations.
15
15
  */
16
16
  export interface SearchContext {
17
- /** The user's permission IDs for filtering results */
18
- userPermissions: string[];
17
+ /** The user's access rule IDs for filtering results */
18
+ userAccessRules: string[];
19
19
  }
20
20
 
21
21
  /**
22
22
  * A command definition for simple registration.
23
- * Permissions will be automatically qualified using the plugin's metadata.
23
+ * Access rules will be automatically qualified using the plugin's metadata.
24
24
  */
25
25
  export interface CommandDefinition {
26
26
  /** Unique command ID (will be prefixed with plugin ID) */
@@ -33,8 +33,8 @@ export interface CommandDefinition {
33
33
  shortcuts?: string[];
34
34
  /** Route to navigate to when executed */
35
35
  route: string;
36
- /** Permissions required (will be auto-qualified with plugin ID) */
37
- requiredPermissions?: Permission[];
36
+ /** Access rules required (will be auto-qualified with plugin ID) */
37
+ requiredAccessRules?: AccessRule[];
38
38
  }
39
39
 
40
40
  /**
@@ -49,7 +49,7 @@ export interface BackendSearchProvider {
49
49
  priority?: number;
50
50
  /**
51
51
  * Search function that returns results matching the query.
52
- * Results should NOT be pre-filtered by permissions - the registry handles that.
52
+ * Results should NOT be pre-filtered by access rules - the registry handles that.
53
53
  */
54
54
  search: (
55
55
  query: string,
@@ -61,13 +61,13 @@ export interface BackendSearchProvider {
61
61
  * Options for registering a search provider.
62
62
  */
63
63
  export interface RegisterSearchProviderOptions {
64
- /** The plugin's metadata - used to qualify permission IDs */
64
+ /** The plugin's metadata - used to qualify access rule IDs */
65
65
  pluginMetadata: PluginMetadata;
66
66
 
67
67
  /**
68
68
  * Simple command definitions. These will be automatically:
69
69
  * - Converted to a search provider
70
- * - Have permissions qualified with pluginId
70
+ * - Have access rules qualified with pluginId
71
71
  * - Be searchable by title, subtitle, and category
72
72
  */
73
73
  commands?: CommandDefinition[];
@@ -75,7 +75,7 @@ export interface RegisterSearchProviderOptions {
75
75
  /**
76
76
  * Custom search provider for more complex search logic.
77
77
  * Use this for entity search (e.g., catalog systems).
78
- * Permissions in returned results will be auto-qualified.
78
+ * Access rules in returned results will be auto-qualified.
79
79
  */
80
80
  provider?: Omit<BackendSearchProvider, "id"> & {
81
81
  /** Provider ID (will be prefixed with plugin ID) */
@@ -93,7 +93,7 @@ const searchProviders = new Map<string, BackendSearchProvider>();
93
93
  * Register a search provider with the command palette.
94
94
  *
95
95
  * This is the main API for plugins to contribute to command palette search.
96
- * Permissions are automatically qualified with the plugin's ID.
96
+ * Access rules are automatically qualified with the plugin's ID.
97
97
  *
98
98
  * @example Simple command registration:
99
99
  * ```ts
@@ -107,7 +107,7 @@ const searchProviders = new Map<string, BackendSearchProvider>();
107
107
  * iconName: "AlertCircle",
108
108
  * shortcuts: ["meta+shift+i", "ctrl+shift+i"],
109
109
  * route: "/incidents?action=create",
110
- * requiredPermissions: [permissions.incidentManage],
110
+ * requiredAccessRules: [access.incidentManage],
111
111
  * },
112
112
  * ],
113
113
  * });
@@ -146,7 +146,7 @@ export function registerSearchProvider(
146
146
  searchProviders.set(commandProvider.id, commandProvider);
147
147
  }
148
148
 
149
- // Register custom provider with auto-qualified permissions
149
+ // Register custom provider with auto-qualified access rules
150
150
  if (provider) {
151
151
  const qualifiedProvider = createQualifiedProvider(pluginMetadata, provider);
152
152
  searchProviders.set(qualifiedProvider.id, qualifiedProvider);
@@ -160,7 +160,7 @@ function createCommandProvider(
160
160
  pluginMetadata: PluginMetadata,
161
161
  commands: CommandDefinition[]
162
162
  ): BackendSearchProvider {
163
- // Pre-qualify all permissions and build SearchResult objects
163
+ // Pre-qualify all access rules and build SearchResult objects
164
164
  const searchableCommands: SearchResult[] = commands.map((cmd) => ({
165
165
  id: `${pluginMetadata.pluginId}.${cmd.id}`,
166
166
  type: "command" as const,
@@ -170,8 +170,8 @@ function createCommandProvider(
170
170
  category: capitalize(pluginMetadata.pluginId),
171
171
  shortcuts: cmd.shortcuts,
172
172
  route: cmd.route,
173
- requiredPermissions: cmd.requiredPermissions?.map((perm) =>
174
- qualifyPermissionId(pluginMetadata, perm)
173
+ requiredAccessRules: cmd.requiredAccessRules?.map((rule) =>
174
+ qualifyAccessRuleId(pluginMetadata, rule)
175
175
  ),
176
176
  }));
177
177
 
@@ -197,7 +197,7 @@ function createCommandProvider(
197
197
  }
198
198
 
199
199
  /**
200
- * Wrap a custom provider to auto-qualify permissions in results.
200
+ * Wrap a custom provider to auto-qualify access rules in results.
201
201
  */
202
202
  function createQualifiedProvider(
203
203
  pluginMetadata: PluginMetadata,
@@ -209,17 +209,17 @@ function createQualifiedProvider(
209
209
  search: async (query, context) => {
210
210
  const results = await provider.search(query, context);
211
211
 
212
- // Auto-qualify permission IDs in results
212
+ // Auto-qualify access rule IDs in results
213
213
  return results.map((result) => ({
214
214
  ...result,
215
215
  id: result.id.includes(".")
216
216
  ? result.id
217
217
  : `${pluginMetadata.pluginId}.${result.id}`,
218
- requiredPermissions: result.requiredPermissions?.map((permId) =>
218
+ requiredAccessRules: result.requiredAccessRules?.map((ruleId) =>
219
219
  // If already qualified (contains the plugin ID prefix), leave it
220
- permId.startsWith(`${pluginMetadata.pluginId}.`)
221
- ? permId
222
- : `${pluginMetadata.pluginId}.${permId}`
220
+ ruleId.startsWith(`${pluginMetadata.pluginId}.`)
221
+ ? ruleId
222
+ : `${pluginMetadata.pluginId}.${ruleId}`
223
223
  ),
224
224
  }));
225
225
  },
package/src/router.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import { implement } from "@orpc/server";
2
- import {
3
- autoAuthMiddleware,
4
- type RpcContext,
5
- } from "@checkstack/backend-api";
2
+ import { autoAuthMiddleware, type RpcContext } from "@checkstack/backend-api";
6
3
  import {
7
4
  commandContract,
8
- filterByPermissions,
5
+ filterByAccessRules,
9
6
  type SearchResult,
10
7
  } from "@checkstack/command-common";
11
8
  import { getSearchProviders } from "./registry";
@@ -13,24 +10,24 @@ import { getSearchProviders } from "./registry";
13
10
  /**
14
11
  * Creates the command router using contract-based implementation.
15
12
  *
16
- * Auth and permissions are automatically enforced via autoAuthMiddleware
17
- * based on the contract's meta.userType and meta.permissions.
13
+ * Auth and access rules are automatically enforced via autoAuthMiddleware
14
+ * based on the contract's meta.userType and meta.access.
18
15
  */
19
16
  const os = implement(commandContract)
20
17
  .$context<RpcContext>()
21
18
  .use(autoAuthMiddleware);
22
19
 
23
20
  /**
24
- * Extract permissions from the context user.
25
- * Only RealUser and ApplicationUser have permissions; ServiceUser doesn't.
21
+ * Extract access rules from the context user.
22
+ * Only RealUser and ApplicationUser have access rules; ServiceUser doesn't.
26
23
  */
27
- function getUserPermissions(context: RpcContext): string[] {
24
+ function getUserAccessRules(context: RpcContext): string[] {
28
25
  const user = context.user;
29
26
  if (!user) return [];
30
27
  if (user.type === "user" || user.type === "application") {
31
- return user.permissions ?? [];
28
+ return user.accessRules ?? [];
32
29
  }
33
- // ServiceUser has no permissions array - treated as having all permissions
30
+ // ServiceUser has no accesss array - treated as having all access
34
31
  // but for search filtering, return empty (no filtering applied)
35
32
  return [];
36
33
  }
@@ -38,21 +35,23 @@ function getUserPermissions(context: RpcContext): string[] {
38
35
  export const createCommandRouter = () => {
39
36
  /**
40
37
  * Search across all registered search providers.
41
- * Results are aggregated from all providers, filtered by permissions,
38
+ * Results are aggregated from all providers, filtered by access rules,
42
39
  * and returned in priority order.
43
40
  */
44
41
  const search = os.search.handler(async ({ input, context }) => {
45
42
  const providers = getSearchProviders();
46
43
  const query = input.query.toLowerCase().trim();
47
44
 
48
- // Get user permissions for filtering
49
- const userPermissions = getUserPermissions(context);
45
+ // Get user access rules for filtering
46
+ const userAccessRules = getUserAccessRules(context);
50
47
 
51
48
  // Execute all provider searches in parallel
52
49
  const providerResults = await Promise.all(
53
50
  providers.map(async (provider) => {
54
51
  try {
55
- const results = await provider.search(query, { userPermissions });
52
+ const results = await provider.search(query, {
53
+ userAccessRules: userAccessRules,
54
+ });
56
55
  return results;
57
56
  } catch (error) {
58
57
  // Log but don't fail - one failing provider shouldn't break search
@@ -62,25 +61,27 @@ export const createCommandRouter = () => {
62
61
  })
63
62
  );
64
63
 
65
- // Flatten and filter by permissions
64
+ // Flatten and filter by access rules
66
65
  const allResults = providerResults.flat();
67
- return filterByPermissions(allResults, userPermissions);
66
+ return filterByAccessRules(allResults, userAccessRules);
68
67
  });
69
68
 
70
69
  /**
71
70
  * Get all registered commands for browsing.
72
- * Returns commands filtered by user permissions.
71
+ * Returns commands filtered by user access rules.
73
72
  */
74
73
  const getCommands = os.getCommands.handler(async ({ context }) => {
75
74
  const providers = getSearchProviders();
76
- const userPermissions = getUserPermissions(context);
75
+ const userAccessRules = getUserAccessRules(context);
77
76
 
78
77
  // Get all results with empty query (commands return all when query is empty)
79
78
  const providerResults = await Promise.all(
80
79
  providers.map(async (provider) => {
81
80
  try {
82
81
  // Empty query = return all items
83
- const results = await provider.search("", { userPermissions });
82
+ const results = await provider.search("", {
83
+ userAccessRules: userAccessRules,
84
+ });
84
85
  // Filter to only commands for this endpoint
85
86
  return results.filter(
86
87
  (r): r is SearchResult & { type: "command" } => r.type === "command"
@@ -93,7 +94,7 @@ export const createCommandRouter = () => {
93
94
  );
94
95
 
95
96
  const allCommands = providerResults.flat();
96
- return filterByPermissions(allCommands, userPermissions);
97
+ return filterByAccessRules(allCommands, userAccessRules);
97
98
  });
98
99
 
99
100
  return os.router({