@opensaas/stack-core 0.38.0 → 0.39.0

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.
Files changed (204) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +123 -0
  3. package/CLAUDE.md +21 -0
  4. package/dist/access/access-filter.d.ts +46 -1
  5. package/dist/access/access-filter.d.ts.map +1 -1
  6. package/dist/access/access-filter.js +113 -4
  7. package/dist/access/access-filter.js.map +1 -1
  8. package/dist/access/declared-dependencies.d.ts.map +1 -1
  9. package/dist/access/declared-dependencies.js +4 -9
  10. package/dist/access/declared-dependencies.js.map +1 -1
  11. package/dist/access/engine.d.ts +0 -20
  12. package/dist/access/engine.d.ts.map +1 -1
  13. package/dist/access/engine.js +0 -25
  14. package/dist/access/engine.js.map +1 -1
  15. package/dist/access/errors.d.ts +49 -0
  16. package/dist/access/errors.d.ts.map +1 -1
  17. package/dist/access/errors.js +75 -0
  18. package/dist/access/errors.js.map +1 -1
  19. package/dist/access/field-access.d.ts +39 -1
  20. package/dist/access/field-access.d.ts.map +1 -1
  21. package/dist/access/field-access.js +108 -42
  22. package/dist/access/field-access.js.map +1 -1
  23. package/dist/access/field-access.test.js +229 -2
  24. package/dist/access/field-access.test.js.map +1 -1
  25. package/dist/access/field-transforms.d.ts +0 -27
  26. package/dist/access/field-transforms.d.ts.map +1 -1
  27. package/dist/access/field-visibility.d.ts +0 -4
  28. package/dist/access/field-visibility.d.ts.map +1 -1
  29. package/dist/access/field-visibility.js +13 -39
  30. package/dist/access/field-visibility.js.map +1 -1
  31. package/dist/access/index.d.ts +6 -2
  32. package/dist/access/index.d.ts.map +1 -1
  33. package/dist/access/index.js +12 -2
  34. package/dist/access/index.js.map +1 -1
  35. package/dist/access/query-validation.d.ts +121 -0
  36. package/dist/access/query-validation.d.ts.map +1 -0
  37. package/dist/access/query-validation.js +292 -0
  38. package/dist/access/query-validation.js.map +1 -0
  39. package/dist/access/relationship-count.d.ts.map +1 -1
  40. package/dist/access/relationship-count.js +2 -10
  41. package/dist/access/relationship-count.js.map +1 -1
  42. package/dist/access/relationship-label-filter.d.ts +23 -23
  43. package/dist/access/relationship-label-filter.d.ts.map +1 -1
  44. package/dist/access/relationship-label-filter.js +5 -76
  45. package/dist/access/relationship-label-filter.js.map +1 -1
  46. package/dist/access/relationship-label-filter.test.js +19 -56
  47. package/dist/access/relationship-label-filter.test.js.map +1 -1
  48. package/dist/access/transaction-registry.d.ts +45 -0
  49. package/dist/access/transaction-registry.d.ts.map +1 -0
  50. package/dist/access/transaction-registry.js +37 -0
  51. package/dist/access/transaction-registry.js.map +1 -0
  52. package/dist/access/types.d.ts +55 -59
  53. package/dist/access/types.d.ts.map +1 -1
  54. package/dist/config/index.d.ts +9 -7
  55. package/dist/config/index.d.ts.map +1 -1
  56. package/dist/config/index.js +10 -18
  57. package/dist/config/index.js.map +1 -1
  58. package/dist/config/label.d.ts +6 -13
  59. package/dist/config/label.d.ts.map +1 -1
  60. package/dist/config/label.js +6 -13
  61. package/dist/config/label.js.map +1 -1
  62. package/dist/config/nav-count.d.ts +11 -19
  63. package/dist/config/nav-count.d.ts.map +1 -1
  64. package/dist/config/nav-count.js +11 -19
  65. package/dist/config/nav-count.js.map +1 -1
  66. package/dist/config/plugin-engine.d.ts +0 -10
  67. package/dist/config/plugin-engine.d.ts.map +1 -1
  68. package/dist/config/plugin-engine.js +1 -37
  69. package/dist/config/plugin-engine.js.map +1 -1
  70. package/dist/config/types.d.ts +94 -109
  71. package/dist/config/types.d.ts.map +1 -1
  72. package/dist/context/apply-defaults.d.ts +8 -31
  73. package/dist/context/apply-defaults.d.ts.map +1 -1
  74. package/dist/context/apply-defaults.js +12 -41
  75. package/dist/context/apply-defaults.js.map +1 -1
  76. package/dist/context/hook-pipeline.d.ts +4 -29
  77. package/dist/context/hook-pipeline.d.ts.map +1 -1
  78. package/dist/context/hook-pipeline.js +8 -41
  79. package/dist/context/hook-pipeline.js.map +1 -1
  80. package/dist/context/index.d.ts +4 -15
  81. package/dist/context/index.d.ts.map +1 -1
  82. package/dist/context/index.js +190 -193
  83. package/dist/context/index.js.map +1 -1
  84. package/dist/context/nested-operations.d.ts +7 -26
  85. package/dist/context/nested-operations.d.ts.map +1 -1
  86. package/dist/context/nested-operations.js +73 -171
  87. package/dist/context/nested-operations.js.map +1 -1
  88. package/dist/context/transaction-boundary.d.ts +36 -47
  89. package/dist/context/transaction-boundary.d.ts.map +1 -1
  90. package/dist/context/transaction-boundary.js +92 -79
  91. package/dist/context/transaction-boundary.js.map +1 -1
  92. package/dist/context/write-pipeline.d.ts +25 -64
  93. package/dist/context/write-pipeline.d.ts.map +1 -1
  94. package/dist/context/write-pipeline.js +81 -111
  95. package/dist/context/write-pipeline.js.map +1 -1
  96. package/dist/fields/format-prisma-default.d.ts.map +1 -1
  97. package/dist/fields/format-prisma-default.js +4 -7
  98. package/dist/fields/format-prisma-default.js.map +1 -1
  99. package/dist/fields/index.d.ts +17 -2
  100. package/dist/fields/index.d.ts.map +1 -1
  101. package/dist/fields/index.js +136 -147
  102. package/dist/fields/index.js.map +1 -1
  103. package/dist/filter/collect.d.ts +26 -3
  104. package/dist/filter/collect.d.ts.map +1 -1
  105. package/dist/filter/collect.js +30 -12
  106. package/dist/filter/collect.js.map +1 -1
  107. package/dist/filter/filter.test.js +98 -15
  108. package/dist/filter/filter.test.js.map +1 -1
  109. package/dist/filter/index.d.ts +1 -0
  110. package/dist/filter/index.d.ts.map +1 -1
  111. package/dist/filter/index.js.map +1 -1
  112. package/dist/filter/map.d.ts.map +1 -1
  113. package/dist/filter/map.js +0 -5
  114. package/dist/filter/map.js.map +1 -1
  115. package/dist/filter/parse.d.ts.map +1 -1
  116. package/dist/filter/parse.js +2 -9
  117. package/dist/filter/parse.js.map +1 -1
  118. package/dist/filter/serialize.d.ts.map +1 -1
  119. package/dist/filter/serialize.js +2 -4
  120. package/dist/filter/serialize.js.map +1 -1
  121. package/dist/hooks/index.d.ts +4 -42
  122. package/dist/hooks/index.d.ts.map +1 -1
  123. package/dist/hooks/index.js +5 -59
  124. package/dist/hooks/index.js.map +1 -1
  125. package/dist/index.d.ts +3 -1
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +20 -4
  128. package/dist/index.js.map +1 -1
  129. package/dist/internal.d.ts +1 -0
  130. package/dist/internal.d.ts.map +1 -1
  131. package/dist/internal.js +4 -0
  132. package/dist/internal.js.map +1 -1
  133. package/dist/lib/case-utils.d.ts +0 -32
  134. package/dist/lib/case-utils.d.ts.map +1 -1
  135. package/dist/lib/case-utils.js +0 -32
  136. package/dist/lib/case-utils.js.map +1 -1
  137. package/dist/mcp/handler.d.ts +0 -4
  138. package/dist/mcp/handler.d.ts.map +1 -1
  139. package/dist/mcp/handler.js +12 -69
  140. package/dist/mcp/handler.js.map +1 -1
  141. package/dist/query/index.d.ts +26 -227
  142. package/dist/query/index.d.ts.map +1 -1
  143. package/dist/query/index.js +14 -117
  144. package/dist/query/index.js.map +1 -1
  145. package/dist/utils/password.d.ts +0 -68
  146. package/dist/utils/password.d.ts.map +1 -1
  147. package/dist/utils/password.js +7 -76
  148. package/dist/utils/password.js.map +1 -1
  149. package/dist/validation/field-config.d.ts.map +1 -1
  150. package/dist/validation/field-config.js +0 -6
  151. package/dist/validation/field-config.js.map +1 -1
  152. package/dist/validation/schema.d.ts +0 -7
  153. package/dist/validation/schema.d.ts.map +1 -1
  154. package/dist/validation/schema.js +0 -10
  155. package/dist/validation/schema.js.map +1 -1
  156. package/package.json +1 -1
  157. package/src/access/access-filter.ts +152 -5
  158. package/src/access/declared-dependencies.ts +4 -9
  159. package/src/access/engine.ts +0 -25
  160. package/src/access/errors.ts +81 -0
  161. package/src/access/field-access.test.ts +312 -2
  162. package/src/access/field-access.ts +118 -42
  163. package/src/access/field-transforms.ts +8 -39
  164. package/src/access/field-visibility.ts +16 -47
  165. package/src/access/index.ts +20 -2
  166. package/src/access/query-validation.ts +359 -0
  167. package/src/access/relationship-count.ts +2 -10
  168. package/src/access/relationship-label-filter.test.ts +19 -68
  169. package/src/access/relationship-label-filter.ts +27 -104
  170. package/src/access/transaction-registry.ts +58 -0
  171. package/src/access/types.ts +83 -86
  172. package/src/config/index.ts +10 -22
  173. package/src/config/label.ts +6 -13
  174. package/src/config/nav-count.ts +11 -19
  175. package/src/config/plugin-engine.ts +1 -37
  176. package/src/config/types.ts +95 -110
  177. package/src/context/apply-defaults.ts +12 -44
  178. package/src/context/hook-pipeline.ts +12 -67
  179. package/src/context/index.ts +263 -247
  180. package/src/context/nested-operations.ts +94 -226
  181. package/src/context/transaction-boundary.ts +113 -109
  182. package/src/context/write-pipeline.ts +99 -155
  183. package/src/fields/format-prisma-default.ts +4 -7
  184. package/src/fields/index.ts +155 -147
  185. package/src/filter/collect.ts +48 -12
  186. package/src/filter/filter.test.ts +153 -15
  187. package/src/filter/index.ts +1 -0
  188. package/src/filter/map.ts +0 -5
  189. package/src/filter/parse.ts +2 -9
  190. package/src/filter/serialize.ts +2 -4
  191. package/src/hooks/index.ts +5 -59
  192. package/src/index.ts +23 -4
  193. package/src/internal.ts +5 -0
  194. package/src/lib/case-utils.ts +0 -32
  195. package/src/mcp/handler.ts +13 -71
  196. package/src/query/index.ts +29 -235
  197. package/src/utils/password.ts +7 -76
  198. package/src/validation/field-config.ts +0 -6
  199. package/src/validation/schema.ts +0 -10
  200. package/tests/context.test.ts +700 -1
  201. package/tests/field-types.test.ts +222 -0
  202. package/tests/mcp-handler.test.ts +34 -0
  203. package/tests/transaction-boundary-outermost.test.ts +530 -0
  204. package/tsconfig.tsbuildinfo +1 -1
@@ -8,16 +8,11 @@ import type {
8
8
  import { executePlugins } from './plugin-engine.js'
9
9
  import type { AccessControl } from '../access/types.js'
10
10
 
11
- /**
12
- * Normalize access control shorthand to object form
13
- * Converts function shorthand to { operation: { query, create, update, delete } } form
14
- */
15
11
  function normalizeListAccess<T>(
16
12
  access: ListAccessControl<T> | undefined,
17
13
  ): { operation?: OperationAccess<T> } | undefined {
18
14
  if (!access) return undefined
19
15
 
20
- // If it's a function, convert to object form applying to all operations
21
16
  if (typeof access === 'function') {
22
17
  const fn = access as AccessControl<T>
23
18
  return {
@@ -30,32 +25,31 @@ function normalizeListAccess<T>(
30
25
  }
31
26
  }
32
27
 
33
- // Already in object form
34
28
  return access
35
29
  }
36
30
 
37
31
  /**
38
- * Helper function to define configuration with type safety
39
- * Executes plugins if present in config.plugins array
32
+ * Define an OpenSaas config, executing `plugins` if present.
40
33
  *
41
- * Note: This function is now async when plugins are present
42
- * For synchronous config definition without plugins, config can still be returned directly
34
+ * Returns synchronously when there are no plugins (backward compatible);
35
+ * returns a `Promise` when plugins are present, since plugin execution is
36
+ * async.
43
37
  */
44
38
  export function config(userConfig: OpenSaasConfig): OpenSaasConfig | Promise<OpenSaasConfig> {
45
- // If no plugins, return config as-is (synchronous, backward compatible)
46
39
  if (!userConfig.plugins || userConfig.plugins.length === 0) {
47
40
  return userConfig
48
41
  }
49
42
 
50
- // Execute plugins and return promise
51
43
  return executePlugins(userConfig)
52
44
  }
53
45
 
54
46
  /**
55
- * Helper function to define a list with type safety
47
+ * Define a list with type safety.
56
48
  *
57
- * Accepts raw field configs and transforms them to inject the item type
58
- * This enables proper typing in field hooks where item is typed correctly
49
+ * `ListConfigInput<TTypeInfo>` accepts raw field configs; the return type
50
+ * `ListConfig<TTypeInfo>` injects `TTypeInfo` so hook/field callbacks (e.g.
51
+ * `resolveInput`'s `item`) are properly typed once `TTypeInfo` is supplied
52
+ * from generated types.
59
53
  *
60
54
  * @example
61
55
  * ```typescript
@@ -108,18 +102,15 @@ export function config(userConfig: OpenSaasConfig): OpenSaasConfig | Promise<Ope
108
102
  export function list<TTypeInfo extends import('./types.js').TypeInfo>(
109
103
  config: ListConfigInput<TTypeInfo>,
110
104
  ): ListConfig<TTypeInfo> {
111
- // Normalize access control shorthand to object form
112
105
  const normalizedConfig = {
113
106
  ...config,
114
107
  access: normalizeListAccess(config.access),
115
108
  }
116
109
 
117
- // At runtime, field configs are unchanged
118
- // At type level, they're transformed to inject TypeInfo types
110
+ // Runtime shape is unchanged; the cast only narrows the TS type to inject TTypeInfo.
119
111
  return normalizedConfig as ListConfig<TTypeInfo>
120
112
  }
121
113
 
122
- // Re-export all types
123
114
  export type {
124
115
  OpenSaasConfig,
125
116
  OutputConfig,
@@ -171,16 +162,13 @@ export type {
171
162
  FileMetadata,
172
163
  ImageMetadata,
173
164
  ImageTransformationResult,
174
- // Plugin system types
175
165
  Plugin,
176
166
  PluginContext,
177
167
  GeneratedFiles,
178
- // List-level hook argument types
179
168
  ResolveInputHookArgs,
180
169
  ValidateHookArgs,
181
170
  BeforeOperationHookArgs,
182
171
  AfterOperationHookArgs,
183
- // Field-level hook argument types
184
172
  FieldResolveInputHookArgs,
185
173
  FieldValidateHookArgs,
186
174
  FieldBeforeOperationHookArgs,
@@ -1,16 +1,11 @@
1
1
  import type { ListConfig } from './types.js'
2
2
 
3
3
  /**
4
- * Resolve the field name that represents a list's rows as a single label —
5
- * the projection half of the label seam. `getItemLabel` reads exactly the
6
- * field this returns, so the field chosen for projection can never drift
7
- * from the field used for rendering.
4
+ * Resolve the field name used as a list's Label field: `ui.labelField` if
5
+ * set, else `name`, else `title`, else `id`.
8
6
  *
9
- * Fallback order: configured `ui.labelField` `name` `title` `id`
10
- * (first field that exists on the list).
11
- *
12
- * @throws if `ui.labelField` is set but does not reference a declared,
13
- * non-relationship field on the list.
7
+ * @throws if `ui.labelField` names a field not declared on the list, or a
8
+ * relationship field (the Label field must be a scalar).
14
9
  */
15
10
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
16
11
  export function getLabelFieldName(listConfig: ListConfig<any>): string {
@@ -37,10 +32,8 @@ export function getLabelFieldName(listConfig: ListConfig<any>): string {
37
32
  }
38
33
 
39
34
  /**
40
- * Resolve the display label for a single row the render half of the label
41
- * seam. Reads the field `getLabelFieldName` resolves, falling back to `id`
42
- * when that field is missing from the row (e.g. stripped by field-level
43
- * access).
35
+ * Render a row's Label field ({@link getLabelFieldName}) as text, falling
36
+ * back to `item.id` when the value is `null`/`undefined`.
44
37
  */
45
38
  export function getItemLabel(
46
39
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
@@ -13,13 +13,12 @@ type CountDelegate = {
13
13
 
14
14
  /**
15
15
  * Whether a list's query access is **statically denied** — provably no session
16
- * can read any row without evaluating a session-dependent function.
16
+ * can read any row, without evaluating a session-dependent function.
17
17
  *
18
- * Mirrors the list view's `canDeleteList` static check (issue #733): query
19
- * access is statically denied when it is absent (deny-by-default, matching the
20
- * access engine) or the literal boolean `false`. A function or `true`
21
- * cannot be decided up front, so the count is fetched through the secured
22
- * context, which re-applies the real rule per row.
18
+ * Mirrors the list view's `canDeleteList` static check (issue #733): absent
19
+ * access is deny-by-default (matching the access engine), so only an explicit
20
+ * `false` counts as statically denied here a function or `true` falls
21
+ * through to a real per-row check via the secured context.
23
22
  *
24
23
  * A statically-denied list must render no nav count at all: a `0` there would
25
24
  * imply "this list is empty" when the truth is "you may see none of it".
@@ -35,20 +34,13 @@ export function isListQueryStaticallyDenied(
35
34
  }
36
35
 
37
36
  /**
38
- * Resolve the access-scoped nav counts for the lists that opt in via
39
- * `ui.navCount` (issue #735).
37
+ * Resolve the access-scoped nav counts for lists that opt in via
38
+ * `ui.navCount` (issue #735). A list gets an entry only if it opts in, isn't
39
+ * a singleton (no meaningful count), and isn't {@link isListQueryStaticallyDenied}
40
+ * — everything else is omitted from the map, never zeroed.
40
41
  *
41
- * The returned map is keyed by list key and contains an entry **only** for a
42
- * list that:
43
- *
44
- * 1. opts in (`ui.navCount === true`) — no count query runs for any other list,
45
- * 2. is not a singleton (a single-record list has no meaningful count), and
46
- * 3. does not have statically-denied query access (see
47
- * {@link isListQueryStaticallyDenied}).
48
- *
49
- * Each count is read through the secured `context.db`, whose `count` applies the
50
- * access filter, so the number reflects exactly what the current session may
51
- * see (a denied row is not counted). Counts are fetched concurrently.
42
+ * Each count is read through the secured `context.db`, so it reflects
43
+ * exactly what the current session may see.
52
44
  */
53
45
  export async function resolveNavCounts(
54
46
  context: AccessContext,
@@ -8,17 +8,12 @@ import type {
8
8
  BaseFieldConfig,
9
9
  } from './types.js'
10
10
 
11
- /**
12
- * Topological sort for plugin dependency resolution
13
- * Ensures plugins execute in correct order based on dependencies
14
- */
15
11
  function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] {
16
12
  const pluginMap = new Map<string, Plugin>()
17
13
  const visited = new Set<string>()
18
14
  const visiting = new Set<string>()
19
15
  const sorted: Plugin[] = []
20
16
 
21
- // Build plugin map
22
17
  for (const plugin of plugins) {
23
18
  if (pluginMap.has(plugin.name)) {
24
19
  throw new Error(`Duplicate plugin name: ${plugin.name}`)
@@ -42,7 +37,6 @@ function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] {
42
37
 
43
38
  visiting.add(pluginName)
44
39
 
45
- // Visit dependencies first
46
40
  if (plugin.dependencies) {
47
41
  for (const dep of plugin.dependencies) {
48
42
  visit(dep, [...path, pluginName])
@@ -54,7 +48,6 @@ function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] {
54
48
  sorted.push(plugin)
55
49
  }
56
50
 
57
- // Visit all plugins
58
51
  for (const plugin of plugins) {
59
52
  visit(plugin.name)
60
53
  }
@@ -62,17 +55,12 @@ function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] {
62
55
  return sorted
63
56
  }
64
57
 
65
- /**
66
- * Merge hooks from extension into existing hooks
67
- * Combines hook arrays so multiple plugins can add hooks
68
- */
69
58
  function mergeHooks(existing: Hooks | undefined, extension: Hooks | undefined): Hooks | undefined {
70
59
  if (!extension) return existing
71
60
  if (!existing) return extension
72
61
 
73
62
  const merged: Partial<Hooks> = {}
74
63
 
75
- // Merge resolveInput hooks (chain them, passing resolvedData through)
76
64
  if (existing.resolveInput || extension.resolveInput) {
77
65
  if (existing.resolveInput && extension.resolveInput) {
78
66
  merged.resolveInput = async (args) => {
@@ -85,7 +73,6 @@ function mergeHooks(existing: Hooks | undefined, extension: Hooks | undefined):
85
73
  }
86
74
  }
87
75
 
88
- // Merge validateInput hooks (run both)
89
76
  if (existing.validateInput || extension.validateInput) {
90
77
  if (existing.validateInput && extension.validateInput) {
91
78
  merged.validateInput = async (args) => {
@@ -97,7 +84,6 @@ function mergeHooks(existing: Hooks | undefined, extension: Hooks | undefined):
97
84
  }
98
85
  }
99
86
 
100
- // Merge beforeOperation hooks (run both in sequence)
101
87
  if (existing.beforeOperation || extension.beforeOperation) {
102
88
  if (existing.beforeOperation && extension.beforeOperation) {
103
89
  merged.beforeOperation = async (args) => {
@@ -109,7 +95,6 @@ function mergeHooks(existing: Hooks | undefined, extension: Hooks | undefined):
109
95
  }
110
96
  }
111
97
 
112
- // Merge afterOperation hooks (run both in sequence)
113
98
  if (existing.afterOperation || extension.afterOperation) {
114
99
  if (existing.afterOperation && extension.afterOperation) {
115
100
  merged.afterOperation = async (args) => {
@@ -124,33 +109,24 @@ function mergeHooks(existing: Hooks | undefined, extension: Hooks | undefined):
124
109
  return Object.keys(merged).length > 0 ? (merged as Hooks) : undefined
125
110
  }
126
111
 
127
- /**
128
- * Execute plugins and transform config
129
- * Returns modified config with plugin data attached
130
- */
131
112
  export async function executePlugins(config: OpenSaasConfig): Promise<OpenSaasConfig> {
132
113
  if (!config.plugins || config.plugins.length === 0) {
133
114
  return config
134
115
  }
135
116
 
136
- // Sort plugins by dependencies
137
117
  const sortedPlugins = sortPluginsByDependencies(config.plugins)
138
118
 
139
- // Initialize mutable config state - preserve all config properties
140
119
  let currentConfig: OpenSaasConfig = {
141
120
  ...config,
142
121
  lists: { ...config.lists }, // Clone lists object to avoid mutating original
143
122
  _pluginData: {},
144
123
  }
145
124
 
146
- // Field type registry (for third-party fields)
147
125
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Registry must accept any field config builder
148
126
  const fieldTypeRegistry = new Map<string, (options?: unknown) => BaseFieldConfig<any>>()
149
127
 
150
- // MCP tools registry
151
128
  const mcpToolsRegistry: McpCustomTool[] = []
152
129
 
153
- // Execute each plugin
154
130
  for (const plugin of sortedPlugins) {
155
131
  const context: PluginContext = {
156
132
  config: currentConfig,
@@ -186,16 +162,13 @@ export async function executePlugins(config: OpenSaasConfig): Promise<OpenSaasCo
186
162
  )
187
163
  }
188
164
 
189
- // Deep merge fields
190
165
  const mergedFields = {
191
166
  ...existing.fields,
192
167
  ...extension.fields,
193
168
  }
194
169
 
195
- // Merge hooks
196
170
  const mergedHooks = mergeHooks(existing.hooks, extension.hooks)
197
171
 
198
- // Merge MCP config
199
172
  const mergedMcp = extension.mcp
200
173
  ? {
201
174
  ...existing.mcp,
@@ -233,11 +206,9 @@ export async function executePlugins(config: OpenSaasConfig): Promise<OpenSaasCo
233
206
  },
234
207
  }
235
208
 
236
- // Execute plugin init
237
209
  await plugin.init(context)
238
210
  }
239
211
 
240
- // Store registered MCP tools in config (if any)
241
212
  if (mcpToolsRegistry.length > 0) {
242
213
  if (!currentConfig._pluginData) {
243
214
  currentConfig._pluginData = {}
@@ -245,8 +216,7 @@ export async function executePlugins(config: OpenSaasConfig): Promise<OpenSaasCo
245
216
  currentConfig._pluginData.__mcpTools = mcpToolsRegistry
246
217
  }
247
218
 
248
- // Store plugin instances in config for runtime access
249
- // This allows context creation to call plugin.runtime() functions
219
+ // Stored so context creation can call each plugin's runtime() function.
250
220
  if (!currentConfig._plugins) {
251
221
  currentConfig._plugins = []
252
222
  }
@@ -255,9 +225,6 @@ export async function executePlugins(config: OpenSaasConfig): Promise<OpenSaasCo
255
225
  return currentConfig
256
226
  }
257
227
 
258
- /**
259
- * Execute beforeGenerate hooks from plugins
260
- */
261
228
  export async function executeBeforeGenerateHooks(config: OpenSaasConfig): Promise<OpenSaasConfig> {
262
229
  if (!config.plugins || config.plugins.length === 0) {
263
230
  return config
@@ -274,9 +241,6 @@ export async function executeBeforeGenerateHooks(config: OpenSaasConfig): Promis
274
241
  return currentConfig
275
242
  }
276
243
 
277
- /**
278
- * Execute afterGenerate hooks from plugins
279
- */
280
244
  export async function executeAfterGenerateHooks(
281
245
  config: OpenSaasConfig,
282
246
  files: { prismaSchema: string; types: string; context: string; [key: string]: string },