@izumisy-tailor/omakase-modules 0.2.0 → 0.4.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 (47) hide show
  1. package/README.md +0 -28
  2. package/docs/generated/README.md +35 -0
  3. package/docs/generated/_media/creating-modules.md +471 -0
  4. package/docs/{tutorials → generated/_media}/using-modules.md +69 -20
  5. package/docs/generated/builder/README.md +25 -0
  6. package/docs/generated/builder/functions/defineModule.md +60 -0
  7. package/docs/generated/builder/functions/withModuleConfiguration.md +68 -0
  8. package/docs/generated/builder/type-aliases/AnyDefinedModule.md +57 -0
  9. package/docs/generated/builder/type-aliases/ConfiguredDependencies.md +44 -0
  10. package/docs/generated/builder/type-aliases/ConfiguredModule.md +60 -0
  11. package/docs/generated/builder/type-aliases/DefinedModule.md +93 -0
  12. package/docs/generated/builder/type-aliases/DependencyModules.md +29 -0
  13. package/docs/generated/builder/type-aliases/EmptyDependencies.md +34 -0
  14. package/docs/generated/builder/type-aliases/ModuleBuilder.md +124 -0
  15. package/docs/generated/builder/type-aliases/ModuleBuilderProps.md +42 -0
  16. package/docs/generated/builder/type-aliases/ModuleFactoryContext.md +40 -0
  17. package/docs/generated/builder/type-aliases/TablesFromNames.md +28 -0
  18. package/docs/generated/config/README.md +19 -0
  19. package/docs/generated/config/classes/ModuleLoader.md +128 -0
  20. package/docs/generated/config/functions/loadModules.md +79 -0
  21. package/docs/generated/config/sdk/README.md +16 -0
  22. package/docs/generated/config/sdk/functions/getModulesReference.md +81 -0
  23. package/docs/generated/config/sdk/functions/loadModuleForDev.md +53 -0
  24. package/docs/generated/config/sdk/type-aliases/GetModulesReferenceOptions.md +60 -0
  25. package/docs/generated/config/type-aliases/LoadedModules.md +162 -0
  26. package/docs/generated/modules.md +11 -0
  27. package/package.json +17 -18
  28. package/src/builder/helpers.ts +388 -28
  29. package/src/builder/index.ts +8 -1
  30. package/src/builder/register.ts +38 -25
  31. package/src/config/module-loader.ts +251 -21
  32. package/src/config/sdk/dev-context.ts +82 -0
  33. package/src/config/sdk/index.ts +2 -1
  34. package/src/config/sdk/paths.ts +124 -13
  35. package/src/config/sdk/wrapper/base.ts +185 -0
  36. package/src/config/sdk/wrapper/generator.ts +89 -0
  37. package/src/config/sdk/wrapper/strategies.ts +121 -0
  38. package/docs/examples/data-models/core/inventory-module.md +0 -230
  39. package/docs/examples/data-models/core/order-module.md +0 -132
  40. package/docs/examples/data-models/scenarios/inventory-reservation-scenario.md +0 -73
  41. package/docs/examples/data-models/scenarios/multi-storefront-order-scenario.md +0 -99
  42. package/docs/examples/data-models/scenarios/order-payment-status-scenario.md +0 -92
  43. package/docs/examples/data-models/scenarios/procurement-order-scenario.md +0 -95
  44. package/docs/tutorials/creating-modules.md +0 -256
  45. package/src/config/module-registry.ts +0 -22
  46. package/src/stub-loader/index.ts +0 -3
  47. package/src/stub-loader/interface.ts +0 -40
@@ -1,21 +1,93 @@
1
- import { ConfiguredModule } from "../builder/helpers";
2
- import {
3
- clearModuleRegistry,
4
- getConfiguredModule,
5
- registerConfiguredModules,
6
- } from "./module-registry";
1
+ import type { ConfiguredModule } from "../builder/helpers";
7
2
 
8
3
  /**
9
- * Module loader
10
- * Builder for registering modules within loadModules
4
+ * Module loader for registering and configuring modules.
5
+ *
6
+ * The `ModuleLoader` is used within the {@link loadModules} function to register
7
+ * configured modules. Modules can depend on each other, and the loader ensures
8
+ * proper dependency management.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { loadModules } from "@izumisy-tailor/omakase-modules";
13
+ * import commerceModule from "omakase-module-commerce-core";
14
+ * import orderModule from "omakase-module-order";
15
+ * import inventoryModule from "omakase-module-inventory";
16
+ *
17
+ * export default loadModules((loader) => {
18
+ * // Add a module without dependencies
19
+ * const $commerce = loader.add(
20
+ * commerceModule.configure({
21
+ * config: {
22
+ * dataModel: {
23
+ * product: { docNumberPrefix: "PROD" },
24
+ * },
25
+ * },
26
+ * })
27
+ * );
28
+ *
29
+ * // Add a module that depends on commerce
30
+ * const $order = loader.add(
31
+ * orderModule.configure({
32
+ * config: { dataModel: {} },
33
+ * dependencies: { commerce: $commerce },
34
+ * })
35
+ * );
36
+ *
37
+ * // Add a module that depends on both commerce and order
38
+ * loader.add(
39
+ * inventoryModule.configure({
40
+ * config: { dbNamespace: "main-db" },
41
+ * dependencies: { commerce: $commerce, order: $order },
42
+ * })
43
+ * );
44
+ *
45
+ * return loader;
46
+ * });
47
+ * ```
11
48
  */
12
49
  export class ModuleLoader {
13
50
  private modules: Array<ConfiguredModule<any>> = [];
14
51
 
15
52
  /**
16
- * Add a module to the loader
17
- * @param module Configured module
18
- * @returns The added module (can be used as a dependency for other modules)
53
+ * Add a configured module to the loader.
54
+ *
55
+ * Returns the added module so it can be used as a dependency for other modules.
56
+ * This enables type-safe dependency wiring between modules.
57
+ *
58
+ * @param module - A configured module created by calling `moduleDefinition.configure()`
59
+ * @returns The same configured module, to be used as a dependency for other modules
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * loadModules((loader) => {
64
+ * // Capture the returned value to use as a dependency
65
+ * const $commerce = loader.add(
66
+ * commerceModule.configure({
67
+ * config: {
68
+ * dataModel: {
69
+ * product: {
70
+ * docNumberPrefix: "PP-PROD",
71
+ * customAttributes: {
72
+ * customStatus: db.enum(["new", "used", "refurbished"]),
73
+ * },
74
+ * },
75
+ * },
76
+ * },
77
+ * })
78
+ * );
79
+ *
80
+ * // Use the captured module as a dependency
81
+ * loader.add(
82
+ * orderModule.configure({
83
+ * config: { dataModel: {} },
84
+ * dependencies: { commerce: $commerce },
85
+ * })
86
+ * );
87
+ *
88
+ * return loader;
89
+ * });
90
+ * ```
19
91
  */
20
92
  add<C extends Record<string, unknown>>(
21
93
  module: ConfiguredModule<C>
@@ -33,29 +105,180 @@ export class ModuleLoader {
33
105
  }
34
106
  }
35
107
 
108
+ /**
109
+ * The result of loading modules, providing access to module configurations and tables.
110
+ *
111
+ * This type is returned by {@link loadModules} and passed to module factory functions.
112
+ * It provides utilities for:
113
+ * - Accessing configured module instances
114
+ * - Loading module configurations
115
+ * - Retrieving tables from dependency modules
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // In tailor.config.ts - using getModulesReference with LoadedModules
120
+ * import { getModulesReference } from "@izumisy-tailor/omakase-modules/config/sdk";
121
+ * import modules from "./modules";
122
+ *
123
+ * const moduleReference = await getModulesReference(modules);
124
+ *
125
+ * export default defineConfig({
126
+ * db: {
127
+ * "main-db": {
128
+ * files: ["./src/tailordb/*.ts", ...moduleReference.tailordb],
129
+ * },
130
+ * },
131
+ * // ...
132
+ * });
133
+ * ```
134
+ */
36
135
  export type LoadedModules = {
37
136
  loadedModules: Record<string, ConfiguredModule<any>>;
137
+ /**
138
+ * Load the configuration for a specific module.
139
+ *
140
+ * Use this in module factory functions to access the user-provided configuration.
141
+ * Throws an error if the module has not been registered via `loadModules`.
142
+ *
143
+ * @param module - An object with `packageName` (typically the module definition)
144
+ * @returns An object containing the module's configuration
145
+ * @throws Error if the module has not been configured
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * // In a module's tailordb/index.ts
150
+ * import moduleDef from "../module";
151
+ *
152
+ * export default withModuleConfiguration(moduleDef, (context, loadedModules) => {
153
+ * // Access this module's configuration
154
+ * const { config } = loadedModules.loadConfig<ModuleConfig>(moduleDef);
155
+ * const prefix = config.dataModel?.product?.docNumberPrefix ?? "PROD";
156
+ *
157
+ * return { product: buildProductTable(context) };
158
+ * });
159
+ * ```
160
+ */
38
161
  loadConfig: <C extends Record<string, unknown>>(module: {
39
162
  packageName: string;
40
163
  }) => {
41
164
  config: C;
42
165
  };
166
+ /**
167
+ * Get the tables from a dependency module by calling its factory function.
168
+ *
169
+ * Use this when a module needs to reference tables from another module,
170
+ * such as creating foreign key relationships or using shared types.
171
+ *
172
+ * @param factory - The factory function exported by the dependency module's `tailordb/index.ts`
173
+ * @returns A promise resolving to the tables created by the factory
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * // In inventory-module's tailordb/index.ts
178
+ * import commerceModuleTables from "omakase-module-commerce-core/backend/tailordb";
179
+ * import orderModuleTables from "omakase-module-order/backend/tailordb";
180
+ *
181
+ * export default withModuleConfiguration(
182
+ * moduleDef,
183
+ * async (context, loadedModules) => {
184
+ * // Get tables from commerce module to reference product/productVariant
185
+ * const { product, productVariant } = await loadedModules.getTables(
186
+ * commerceModuleTables
187
+ * );
188
+ *
189
+ * // Get tables from order module to reference order
190
+ * const { order } = await loadedModules.getTables(orderModuleTables);
191
+ *
192
+ * // Use dependency tables when building this module's tables
193
+ * const inventory = buildInventoryTable(context, { product, productVariant });
194
+ * const inventoryTransaction = buildInventoryTransactionTable(context, {
195
+ * inventory,
196
+ * order,
197
+ * });
198
+ *
199
+ * return { inventory, inventoryTransaction };
200
+ * }
201
+ * );
202
+ * ```
203
+ */
204
+ getTables: <T>(
205
+ factory: (loadedModules: LoadedModules) => Promise<T>
206
+ ) => Promise<T>;
207
+ /**
208
+ * Indicates this is a dev context created by loadModuleForDev.
209
+ * When true, wrapper generation uses local src paths instead of node_modules.
210
+ * @internal
211
+ */
212
+ __devContext?: {
213
+ /** The package name of the module being developed */
214
+ moduleName: string;
215
+ };
43
216
  };
44
217
 
45
218
  /**
46
- * Load modules with configuration
47
- * @param configurator Function that receives a loader, registers modules, and returns the loader
48
- * @returns Loaded modules and loadConfig function
219
+ * Load and configure modules for your Tailor application.
220
+ *
221
+ * This is the main entry point for setting up omakase modules. Use this function
222
+ * to register all modules your application needs, configure them with your settings,
223
+ * and wire up dependencies between modules.
224
+ *
225
+ * @param configurator - A function that receives a {@link ModuleLoader} and returns it after adding modules
226
+ * @returns A {@link LoadedModules} object containing all registered modules and utilities
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * // modules.ts - Define your module configuration
231
+ * import { loadModules } from "@izumisy-tailor/omakase-modules";
232
+ * import { db } from "@tailor-platform/sdk";
233
+ * import commerceModule from "omakase-module-commerce-core";
234
+ * import orderModule from "omakase-module-order";
235
+ * import inventoryModule from "omakase-module-inventory";
236
+ *
237
+ * export default loadModules((loader) => {
238
+ * // Configure and add the commerce module
239
+ * const $commerce = loader.add(
240
+ * commerceModule.configure({
241
+ * config: {
242
+ * dataModel: {
243
+ * product: {
244
+ * docNumberPrefix: "PP-PROD",
245
+ * customAttributes: {
246
+ * customStatus: db.enum(["new", "used", "refurbished"]),
247
+ * },
248
+ * },
249
+ * },
250
+ * },
251
+ * })
252
+ * );
253
+ *
254
+ * // Configure order module with commerce as a dependency
255
+ * const $order = loader.add(
256
+ * orderModule.configure({
257
+ * config: { dataModel: {} },
258
+ * dependencies: { commerce: $commerce },
259
+ * })
260
+ * );
261
+ *
262
+ * // Configure inventory module with both dependencies
263
+ * loader.add(
264
+ * inventoryModule.configure({
265
+ * config: {
266
+ * dbNamespace: "main-db",
267
+ * invantoryBootstrapBaseValue: 300,
268
+ * },
269
+ * dependencies: { commerce: $commerce, order: $order },
270
+ * })
271
+ * );
272
+ *
273
+ * return loader;
274
+ * });
275
+ * ```
49
276
  */
50
277
  export const loadModules = (
51
278
  configurator: (loader: ModuleLoader) => ModuleLoader
52
279
  ): LoadedModules => {
53
- clearModuleRegistry();
54
-
55
280
  const emptyLoader = new ModuleLoader();
56
- const modules = registerConfiguredModules(
57
- configurator(emptyLoader)._getModules()
58
- );
281
+ const modules = configurator(emptyLoader)._getModules();
59
282
 
60
283
  const loadedModules = modules.reduce<Record<string, ConfiguredModule<any>>>(
61
284
  (acc, module) => {
@@ -65,12 +288,12 @@ export const loadModules = (
65
288
  {}
66
289
  );
67
290
 
68
- return {
291
+ const loadedModulesResult: LoadedModules = {
69
292
  loadedModules,
70
293
  loadConfig: <C extends Record<string, unknown>>(module: {
71
294
  packageName: string;
72
295
  }) => {
73
- const loadedModule = getConfiguredModule(module.packageName);
296
+ const loadedModule = loadedModules[module.packageName];
74
297
  if (!loadedModule) {
75
298
  throw new Error(
76
299
  `Module "${module.packageName}" has not been configured. Ensure it is added via loadModules.`
@@ -81,5 +304,12 @@ export const loadModules = (
81
304
  config: loadedModule.moduleProps.config as C,
82
305
  };
83
306
  },
307
+ getTables: async <T>(
308
+ factory: (loadedModules: LoadedModules) => Promise<T>
309
+ ): Promise<T> => {
310
+ return factory(loadedModulesResult);
311
+ },
84
312
  };
313
+
314
+ return loadedModulesResult;
85
315
  };
@@ -0,0 +1,82 @@
1
+ import { loadModules, type LoadedModules } from "../module-loader";
2
+ import type { DefinedModule, DependencyModules } from "../../builder/helpers";
3
+
4
+ /**
5
+ * Creates a LoadedModules context for individual module development.
6
+ *
7
+ * This allows using `getModulesReference` in a module's own `tailor.config.ts`,
8
+ * enabling the kysely-type generator to work during module development.
9
+ *
10
+ * Dependencies declared in the module's `defineModule` are automatically
11
+ * registered with default (empty) configurations.
12
+ *
13
+ * @param module The module being developed
14
+ * @param config Optional development configuration. If omitted, uses module.devConfig.
15
+ * @returns A LoadedModules object that can be passed to getModulesReference
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // tailor.config.ts
20
+ * import { loadModuleForDev, getModulesReference } from "@izumisy-tailor/omakase-modules/config/sdk";
21
+ * import inventoryModule from "./src/module";
22
+ *
23
+ * const modules = loadModuleForDev(inventoryModule);
24
+ * const moduleReference = await getModulesReference(modules);
25
+ *
26
+ * export default defineConfig({ ... });
27
+ * ```
28
+ */
29
+ export const loadModuleForDev = <
30
+ C extends Record<string, unknown>,
31
+ Tables extends Record<string, unknown>,
32
+ Deps extends DependencyModules
33
+ >(
34
+ module: DefinedModule<C, Tables, Deps>,
35
+ config?: C
36
+ ): LoadedModules => {
37
+ // Use provided config, fall back to module's devConfig, or use empty object as default
38
+ const resolvedConfig = config ?? module.devConfig ?? ({} as C);
39
+
40
+ const modules = loadModules((loader) => {
41
+ // Recursively register all dependency modules with default config
42
+ const registerDependencies = (deps: DependencyModules) => {
43
+ for (const dep of Object.values(deps)) {
44
+ if (!dep) continue; // Skip empty dependency marker
45
+ // First register transitive dependencies
46
+ if (dep.dependencies && Object.keys(dep.dependencies).length > 0) {
47
+ registerDependencies(dep.dependencies);
48
+ }
49
+ // Then register this dependency with empty config
50
+ // For dev context, we use type assertion since dependencies are auto-resolved
51
+ loader.add(
52
+ (
53
+ dep.configure as (props: { config: Record<string, unknown> }) => any
54
+ )({ config: {} })
55
+ );
56
+ }
57
+ };
58
+
59
+ // Register all dependencies first
60
+ if (module.dependencies && Object.keys(module.dependencies).length > 0) {
61
+ registerDependencies(module.dependencies);
62
+ }
63
+
64
+ // Register the main module being developed
65
+ // For dev context, we don't need to pass dependencies
66
+ loader.add(
67
+ (module.configure as (props: { config: C }) => any)({
68
+ config: resolvedConfig,
69
+ })
70
+ );
71
+
72
+ return loader;
73
+ });
74
+
75
+ // Mark this as a dev context with the module name being developed
76
+ return {
77
+ ...modules,
78
+ __devContext: {
79
+ moduleName: module.packageName,
80
+ },
81
+ };
82
+ };
@@ -1 +1,2 @@
1
- export { getModulesReference } from "./paths";
1
+ export { getModulesReference, type GetModulesReferenceOptions } from "./paths";
2
+ export { loadModuleForDev } from "./dev-context";
@@ -1,20 +1,131 @@
1
1
  import path from "node:path";
2
- import { LoadedModules } from "../module-loader";
2
+ import type { LoadedModules } from "../module-loader";
3
+ import { generateWrapperFiles, OMAKASE_WRAPPER_DIR } from "./wrapper/generator";
3
4
 
4
- export const getModulesReference = (loadedModules: LoadedModules) => {
5
- const modulePackageNames = Object.keys(loadedModules.loadedModules);
5
+ /**
6
+ * Options for configuring {@link getModulesReference} behavior.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const moduleReference = await getModulesReference(modules, {
11
+ * basePath: "/custom/path",
12
+ * silent: true, // Suppress console output
13
+ * });
14
+ * ```
15
+ */
16
+ export type GetModulesReferenceOptions = {
17
+ /**
18
+ * Base path for the application.
19
+ *
20
+ * This determines where wrapper files are generated and where module
21
+ * paths are resolved from. Defaults to `process.cwd()`.
22
+ *
23
+ * @default process.cwd()
24
+ */
25
+ basePath?: string;
26
+ /**
27
+ * Whether to suppress log output.
28
+ *
29
+ * When `true`, no console output will be printed during module loading.
30
+ * Useful for testing or when you want cleaner build output.
31
+ *
32
+ * @default false
33
+ */
34
+ silent?: boolean;
35
+ };
6
36
 
7
- return {
8
- tailordb: modulePackageNames.map((name) =>
9
- path.join("node_modules", name, "src", "tailordb", "*.ts")
10
- ),
37
+ /**
38
+ * Get file path patterns for modules to use in Tailor configuration.
39
+ *
40
+ * This function processes your loaded modules and returns glob patterns
41
+ * that can be spread into your `defineConfig`. It handles all the necessary
42
+ * setup to make module TailorDB types, resolvers, and executors available
43
+ * to Tailor's configuration system.
44
+ *
45
+ * @param loadedModules - The result of calling `loadModules()`
46
+ * @param options - Optional configuration for path resolution and logging
47
+ * @returns An object containing glob patterns for `tailordb`, `resolver`, and `executor` files
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * // tailor.config.ts
52
+ * import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
53
+ * import { getModulesReference } from "@izumisy-tailor/omakase-modules/config/sdk";
54
+ * import modules from "./modules";
55
+ *
56
+ * // Get module path patterns
57
+ * const moduleReference = await getModulesReference(modules);
58
+ *
59
+ * export default defineConfig({
60
+ * name: "my-app",
61
+ *
62
+ * // Spread module paths alongside your local files
63
+ * db: {
64
+ * "main-db": {
65
+ * files: ["./src/tailordb/*.ts", ...moduleReference.tailordb],
66
+ * },
67
+ * },
68
+ *
69
+ * resolver: {
70
+ * "main-pipeline": {
71
+ * files: ["./src/resolvers/*.ts", ...moduleReference.resolver],
72
+ * },
73
+ * },
74
+ *
75
+ * executor: {
76
+ * files: ["./src/executors/*.ts", ...moduleReference.executor],
77
+ * },
78
+ * });
79
+ * ```
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // With custom options
84
+ * const moduleReference = await getModulesReference(modules, {
85
+ * basePath: import.meta.dirname, // Use ESM module directory
86
+ * silent: true, // No console output
87
+ * });
88
+ * ```
89
+ *
90
+ * @remarks
91
+ * The returned object contains three arrays:
92
+ * - `tailordb` - Paths to TailorDB type definitions from modules
93
+ * - `resolver` - Paths to GraphQL resolver definitions from modules
94
+ * - `executor` - Paths to event executor definitions from modules
95
+ *
96
+ * If a module has no files of a certain type, that array will be empty.
97
+ */
98
+ export const getModulesReference = async (
99
+ loadedModules: LoadedModules,
100
+ options: GetModulesReferenceOptions = {}
101
+ ) => {
102
+ const { basePath = process.cwd(), silent = false } = options;
11
103
 
12
- resolver: modulePackageNames.map((name) =>
13
- path.join("node_modules", name, "src", "resolvers", "*.ts")
14
- ),
104
+ // Log loaded modules information
105
+ const modulePackageNames = Object.keys(loadedModules.loadedModules);
106
+ if (!silent) {
107
+ console.log(`[omakase] Loaded ${modulePackageNames.length} module(s):\n`);
108
+ for (const name of modulePackageNames) {
109
+ console.log(` * ${name}`);
110
+ }
111
+ console.log("");
112
+ }
113
+
114
+ // Generate wrapper files and return paths to them
115
+ const wrapperPaths = await generateWrapperFiles(loadedModules, basePath);
15
116
 
16
- executor: modulePackageNames.map((name) =>
17
- path.join("node_modules", name, "src", "executors", "*.ts")
18
- ),
117
+ return {
118
+ tailordb:
119
+ wrapperPaths.tailordb.length > 0
120
+ ? [path.join(OMAKASE_WRAPPER_DIR, "*", "tailordb", "*.ts")]
121
+ : [],
122
+ resolver:
123
+ wrapperPaths.resolver.length > 0
124
+ ? [path.join(OMAKASE_WRAPPER_DIR, "*", "resolvers", "*.ts")]
125
+ : [],
126
+ executor:
127
+ wrapperPaths.executor.length > 0
128
+ ? [path.join(OMAKASE_WRAPPER_DIR, "*", "executors", "*.ts")]
129
+ : [],
19
130
  };
20
131
  };