@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.
- package/README.md +0 -28
- package/docs/generated/README.md +35 -0
- package/docs/generated/_media/creating-modules.md +471 -0
- package/docs/{tutorials → generated/_media}/using-modules.md +69 -20
- package/docs/generated/builder/README.md +25 -0
- package/docs/generated/builder/functions/defineModule.md +60 -0
- package/docs/generated/builder/functions/withModuleConfiguration.md +68 -0
- package/docs/generated/builder/type-aliases/AnyDefinedModule.md +57 -0
- package/docs/generated/builder/type-aliases/ConfiguredDependencies.md +44 -0
- package/docs/generated/builder/type-aliases/ConfiguredModule.md +60 -0
- package/docs/generated/builder/type-aliases/DefinedModule.md +93 -0
- package/docs/generated/builder/type-aliases/DependencyModules.md +29 -0
- package/docs/generated/builder/type-aliases/EmptyDependencies.md +34 -0
- package/docs/generated/builder/type-aliases/ModuleBuilder.md +124 -0
- package/docs/generated/builder/type-aliases/ModuleBuilderProps.md +42 -0
- package/docs/generated/builder/type-aliases/ModuleFactoryContext.md +40 -0
- package/docs/generated/builder/type-aliases/TablesFromNames.md +28 -0
- package/docs/generated/config/README.md +19 -0
- package/docs/generated/config/classes/ModuleLoader.md +128 -0
- package/docs/generated/config/functions/loadModules.md +79 -0
- package/docs/generated/config/sdk/README.md +16 -0
- package/docs/generated/config/sdk/functions/getModulesReference.md +81 -0
- package/docs/generated/config/sdk/functions/loadModuleForDev.md +53 -0
- package/docs/generated/config/sdk/type-aliases/GetModulesReferenceOptions.md +60 -0
- package/docs/generated/config/type-aliases/LoadedModules.md +162 -0
- package/docs/generated/modules.md +11 -0
- package/package.json +17 -18
- package/src/builder/helpers.ts +388 -28
- package/src/builder/index.ts +8 -1
- package/src/builder/register.ts +38 -25
- package/src/config/module-loader.ts +251 -21
- package/src/config/sdk/dev-context.ts +82 -0
- package/src/config/sdk/index.ts +2 -1
- package/src/config/sdk/paths.ts +124 -13
- package/src/config/sdk/wrapper/base.ts +185 -0
- package/src/config/sdk/wrapper/generator.ts +89 -0
- package/src/config/sdk/wrapper/strategies.ts +121 -0
- package/docs/examples/data-models/core/inventory-module.md +0 -230
- package/docs/examples/data-models/core/order-module.md +0 -132
- package/docs/examples/data-models/scenarios/inventory-reservation-scenario.md +0 -73
- package/docs/examples/data-models/scenarios/multi-storefront-order-scenario.md +0 -99
- package/docs/examples/data-models/scenarios/order-payment-status-scenario.md +0 -92
- package/docs/examples/data-models/scenarios/procurement-order-scenario.md +0 -95
- package/docs/tutorials/creating-modules.md +0 -256
- package/src/config/module-registry.ts +0 -22
- package/src/stub-loader/index.ts +0 -3
- package/src/stub-loader/interface.ts +0 -40
package/README.md
CHANGED
|
@@ -29,31 +29,3 @@ pnpm add @izumisy-tailor/omakase-modules
|
|
|
29
29
|
|
|
30
30
|
- [Using Modules](./docs/tutorials/using-modules.md) - How to use pre-built modules in your application
|
|
31
31
|
- [Creating Modules](./docs/tutorials/creating-modules.md) - How to create reusable modules
|
|
32
|
-
|
|
33
|
-
## API Reference
|
|
34
|
-
|
|
35
|
-
### `@izumisy-tailor/omakase-modules`
|
|
36
|
-
|
|
37
|
-
- `loadModules(configurator)` - Load and configure modules in your application
|
|
38
|
-
|
|
39
|
-
### `@izumisy-tailor/omakase-modules/builder`
|
|
40
|
-
|
|
41
|
-
- `defineModule<Config, Tables>(options)` - Define a new module
|
|
42
|
-
- `withModuleConfiguration(module, factory)` - Access module configuration in TailorDB, Resolvers, and Executors
|
|
43
|
-
- `ModuleDependency<T>` - Type utility for declaring module dependencies
|
|
44
|
-
|
|
45
|
-
### `@izumisy-tailor/omakase-modules/config/sdk`
|
|
46
|
-
|
|
47
|
-
- `getModulesReference(loadedModules)` - Generate file path references for `tailor.config.ts`
|
|
48
|
-
|
|
49
|
-
## Project Structure
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
examples/
|
|
53
|
-
commerce-core-module/ # E-commerce core module (Product, Customer, etc.)
|
|
54
|
-
order-module/ # Order management module
|
|
55
|
-
inventory-module/ # Inventory management module
|
|
56
|
-
basic-app/ # Application using the above modules
|
|
57
|
-
packages/
|
|
58
|
-
core/ # omakase-modules core package
|
|
59
|
-
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
**@izumisy-tailor/omakase-modules**
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
# Omakase Modules
|
|
6
|
+
|
|
7
|
+
A **configurable module system** for Tailor Platform. Define reusable modules with type-safe configurations and share them across applications.
|
|
8
|
+
|
|
9
|
+
## Motivation
|
|
10
|
+
|
|
11
|
+
When building applications on Tailor Platform, you often face these challenges:
|
|
12
|
+
|
|
13
|
+
1. **Reusability**: You want to reuse common data models and business logic (e.g., e-commerce, inventory, orders) across multiple applications
|
|
14
|
+
2. **Customizability**: While modules should be reusable, you need to customize them for application-specific requirements
|
|
15
|
+
3. **Dependency Management**: Module dependencies should be explicitly defined and managed in a type-safe manner
|
|
16
|
+
|
|
17
|
+
Omakase Modules is designed to solve these challenges by providing a flexible, type-safe module system.
|
|
18
|
+
|
|
19
|
+
## Key Features
|
|
20
|
+
|
|
21
|
+
- **Module Definition**: Define modules with `defineModule` and declare type-safe configuration schemas
|
|
22
|
+
- **Module Configuration**: Configure modules at the application level, injecting custom attributes and prefixes
|
|
23
|
+
- **Dependency Management**: Explicitly define inter-module dependencies with the `ModuleDependency` type
|
|
24
|
+
- **Configuration Injection**: Inject configurations into TailorDB, Resolvers, and Executors using `withModuleConfiguration`
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @izumisy-tailor/omakase-modules
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
|
|
34
|
+
- [Using Modules](_media/using-modules.md) - How to use pre-built modules in your application
|
|
35
|
+
- [Creating Modules](_media/creating-modules.md) - How to create reusable modules
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# Creating Modules
|
|
2
|
+
|
|
3
|
+
This guide explains how to create reusable Omakase Modules that can be shared across applications.
|
|
4
|
+
|
|
5
|
+
## Module Structure
|
|
6
|
+
|
|
7
|
+
A typical module package has this structure:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
my-module/
|
|
11
|
+
package.json
|
|
12
|
+
tsconfig.json
|
|
13
|
+
src/
|
|
14
|
+
module.ts # Module definition and table types export
|
|
15
|
+
types.ts # Configuration types
|
|
16
|
+
tailordb/
|
|
17
|
+
index.ts # TailorDB exports with tableNames
|
|
18
|
+
my-table.ts # Table builders
|
|
19
|
+
resolvers/
|
|
20
|
+
my-resolver.ts
|
|
21
|
+
executors/
|
|
22
|
+
my-executor.ts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Prerequisites: tsconfig.json Path Aliases
|
|
26
|
+
|
|
27
|
+
Your module's `tsconfig.json` must include the following path aliases for the module system to function correctly:
|
|
28
|
+
|
|
29
|
+
```jsonc
|
|
30
|
+
{
|
|
31
|
+
"compilerOptions": {
|
|
32
|
+
"paths": {
|
|
33
|
+
"@omakase-modules/config": ["./src/module"],
|
|
34
|
+
"@/*": ["./src/*"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Alias | Path | Purpose |
|
|
41
|
+
|-------|------|---------|
|
|
42
|
+
| `@omakase-modules/config` | `["./src/module"]` | **Required**. Points to your module definition file. The wrapper generator uses this alias to import your module's configuration during code generation. |
|
|
43
|
+
| `@/*` | `["./src/*"]` | **Required**. Provides a shorthand for importing from the `src/` directory. Used internally during development mode to resolve local source files. |
|
|
44
|
+
|
|
45
|
+
## 1. Define the Module
|
|
46
|
+
|
|
47
|
+
Create the module definition with `defineModule`. The module definition uses a builder pattern:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// src/module.ts
|
|
51
|
+
import {
|
|
52
|
+
defineModule,
|
|
53
|
+
type TablesFromNames,
|
|
54
|
+
} from "@izumisy-tailor/omakase-modules/builder";
|
|
55
|
+
import type { ModuleConfig } from "./types";
|
|
56
|
+
import { tableNames } from "./tailordb";
|
|
57
|
+
import * as pkg from "../package.json";
|
|
58
|
+
|
|
59
|
+
export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
|
|
60
|
+
packageName: pkg.name,
|
|
61
|
+
})
|
|
62
|
+
.withDevConfig({}) // Optional: default config for development
|
|
63
|
+
.build();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `packageName` must match the `name` field in your `package.json`. The `TablesFromNames` utility type derives the table types from `tableNames`, ensuring they stay in sync.
|
|
67
|
+
|
|
68
|
+
### Builder Methods
|
|
69
|
+
|
|
70
|
+
- `.withDevConfig(config)` - Sets the default configuration used during module development
|
|
71
|
+
- `.withDependencies(deps)` - Declares dependencies on other modules (see section 4)
|
|
72
|
+
- `.build()` - Finalizes the module definition (must be called at the end)
|
|
73
|
+
|
|
74
|
+
## 2. Define Configuration Types
|
|
75
|
+
|
|
76
|
+
Define what configuration options your module accepts:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// src/types.ts
|
|
80
|
+
import { TailorField } from "@tailor-platform/sdk";
|
|
81
|
+
|
|
82
|
+
type DataModelConfiguration = {
|
|
83
|
+
docNumberPrefix?: string;
|
|
84
|
+
customAttributes?: Record<string, TailorField<any>>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type ModuleConfig = {
|
|
88
|
+
dataModel?: {
|
|
89
|
+
product?: DataModelConfiguration;
|
|
90
|
+
category?: DataModelConfiguration;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 3. Build Tables with Configuration
|
|
96
|
+
|
|
97
|
+
Use `withModuleConfiguration` to access configuration when building tables:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// src/tailordb/index.ts
|
|
101
|
+
import {
|
|
102
|
+
withModuleConfiguration,
|
|
103
|
+
type TablesFromNames,
|
|
104
|
+
} from "@izumisy-tailor/omakase-modules/builder";
|
|
105
|
+
import moduleDef from "../module";
|
|
106
|
+
import { buildProductTable } from "./product";
|
|
107
|
+
import { buildCategoryTable } from "./category";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* List of table names exported by this module.
|
|
111
|
+
* This is the single source of truth for table definitions.
|
|
112
|
+
*/
|
|
113
|
+
export const tableNames = ["product", "category"] as const;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Factory function that creates tailordb tables with the given module configuration.
|
|
117
|
+
* This is called by the wrapper files generated by getModulesReference.
|
|
118
|
+
*/
|
|
119
|
+
export default withModuleConfiguration(moduleDef, (context) => {
|
|
120
|
+
const category = buildCategoryTable(context);
|
|
121
|
+
const product = buildProductTable(context, { category });
|
|
122
|
+
|
|
123
|
+
return { product, category } satisfies TablesFromNames<typeof tableNames>;
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **Important**: The `tableNames` export is required for the wrapper generator to create named exports for each table.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// src/tailordb/product.ts
|
|
131
|
+
import { type ModuleFactoryContext } from "@izumisy-tailor/omakase-modules/builder";
|
|
132
|
+
import { db, type TailorDBType } from "@tailor-platform/sdk";
|
|
133
|
+
import type { ModuleConfig } from "../types";
|
|
134
|
+
|
|
135
|
+
export const buildProductTable = (
|
|
136
|
+
{ config }: ModuleFactoryContext<ModuleConfig>,
|
|
137
|
+
deps: { category: TailorDBType }
|
|
138
|
+
) => {
|
|
139
|
+
const customAttributes = config.dataModel?.product?.customAttributes || {};
|
|
140
|
+
const docNumberPrefix = config.dataModel?.product?.docNumberPrefix ?? "PROD";
|
|
141
|
+
|
|
142
|
+
return db.type("Product", {
|
|
143
|
+
name: db.string(),
|
|
144
|
+
sku: db.string().unique(),
|
|
145
|
+
categoryId: db.uuid({ optional: true }).relation({
|
|
146
|
+
type: "keyOnly",
|
|
147
|
+
toward: { type: deps.category },
|
|
148
|
+
}),
|
|
149
|
+
docNumber: db.docNumber({ prefix: docNumberPrefix }),
|
|
150
|
+
...customAttributes,
|
|
151
|
+
...db.fields.timestamps(),
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 4. Declare Dependencies on Other Modules
|
|
157
|
+
|
|
158
|
+
If your module depends on other modules, use `.withDependencies()` in your module definition:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// src/module.ts
|
|
162
|
+
import {
|
|
163
|
+
defineModule,
|
|
164
|
+
type TablesFromNames,
|
|
165
|
+
} from "@izumisy-tailor/omakase-modules/builder";
|
|
166
|
+
import { ModuleConfig } from "./types";
|
|
167
|
+
import { tableNames } from "./tailordb";
|
|
168
|
+
import * as pkg from "../package.json";
|
|
169
|
+
import commerceModule from "omakase-module-commerce-core";
|
|
170
|
+
import orderModule from "omakase-module-order";
|
|
171
|
+
|
|
172
|
+
export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
|
|
173
|
+
packageName: pkg.name,
|
|
174
|
+
})
|
|
175
|
+
.withDevConfig({
|
|
176
|
+
dbNamespace: "main-db",
|
|
177
|
+
})
|
|
178
|
+
.withDependencies({ commerce: commerceModule, order: orderModule })
|
|
179
|
+
.build();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The dependency modules are passed directly to `.withDependencies()`. This ensures type-safe dependency injection when users configure your module.
|
|
183
|
+
|
|
184
|
+
### Using Dependency Tables in TailorDB
|
|
185
|
+
|
|
186
|
+
When your module needs to reference tables from dependency modules, use `loadedModules.getTables()` in an async factory function:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// src/tailordb/index.ts
|
|
190
|
+
import {
|
|
191
|
+
withModuleConfiguration,
|
|
192
|
+
type TablesFromNames,
|
|
193
|
+
} from "@izumisy-tailor/omakase-modules/builder";
|
|
194
|
+
import moduleDef from "../module";
|
|
195
|
+
import { buildInventoryTable } from "./inventory";
|
|
196
|
+
import { buildInventoryTransactionTable } from "./inventory-transaction";
|
|
197
|
+
import { buildStockAdjustmentTable } from "./stock-adjustment";
|
|
198
|
+
import commerceModuleTables from "omakase-module-commerce-core/backend/tailordb";
|
|
199
|
+
import orderModuleTables from "omakase-module-order/backend/tailordb";
|
|
200
|
+
|
|
201
|
+
export const tableNames = [
|
|
202
|
+
"inventory",
|
|
203
|
+
"inventoryTransaction",
|
|
204
|
+
"stockAdjustment",
|
|
205
|
+
] as const;
|
|
206
|
+
|
|
207
|
+
export default withModuleConfiguration(
|
|
208
|
+
moduleDef,
|
|
209
|
+
async (context, loadedModules) => {
|
|
210
|
+
// Get tables from dependency modules
|
|
211
|
+
const { product, productVariant } = await loadedModules.getTables(
|
|
212
|
+
commerceModuleTables
|
|
213
|
+
);
|
|
214
|
+
const { order } = await loadedModules.getTables(orderModuleTables);
|
|
215
|
+
|
|
216
|
+
const inventory = buildInventoryTable(context, { product, productVariant });
|
|
217
|
+
const inventoryTransaction = buildInventoryTransactionTable(context, {
|
|
218
|
+
inventory,
|
|
219
|
+
order,
|
|
220
|
+
});
|
|
221
|
+
const stockAdjustment = buildStockAdjustmentTable(context, {
|
|
222
|
+
inventory,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
inventory,
|
|
227
|
+
inventoryTransaction,
|
|
228
|
+
stockAdjustment,
|
|
229
|
+
} satisfies TablesFromNames<typeof tableNames>;
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Using Dependency Tables in Executors
|
|
235
|
+
|
|
236
|
+
Executors can also access dependency tables:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// src/executors/myExecutor.ts
|
|
240
|
+
import { withModuleConfiguration } from "@izumisy-tailor/omakase-modules/builder";
|
|
241
|
+
import { createExecutor, recordCreatedTrigger } from "@tailor-platform/sdk";
|
|
242
|
+
import commerceModuleTables from "omakase-module-commerce-core/backend/tailordb";
|
|
243
|
+
import moduleDef from "../module";
|
|
244
|
+
|
|
245
|
+
export default withModuleConfiguration(
|
|
246
|
+
moduleDef,
|
|
247
|
+
async (_context, loadedModules) => {
|
|
248
|
+
const { productVariant } = await loadedModules.getTables(
|
|
249
|
+
commerceModuleTables
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return createExecutor({
|
|
253
|
+
name: "my-executor",
|
|
254
|
+
trigger: recordCreatedTrigger({ type: productVariant }),
|
|
255
|
+
operation: { /* ... */ },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## File Export Requirements
|
|
262
|
+
|
|
263
|
+
Due to Tailor SDK API requirements, **TailorDB** and **Resolvers/Executors** have different export patterns:
|
|
264
|
+
|
|
265
|
+
**TailorDB**: Should use a single `index.ts` barrel export with a `tableNames` constant and a default factory function. This is **strongly recommended** because:
|
|
266
|
+
1. It allows you to use `satisfies` to verify that all required tables are defined
|
|
267
|
+
2. The `tableNames` export enables the wrapper generator to create named exports
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// src/tailordb/index.ts
|
|
271
|
+
export const tableNames = ["product", "category"] as const;
|
|
272
|
+
|
|
273
|
+
export default withModuleConfiguration(moduleDef, (context) => {
|
|
274
|
+
const category = buildCategoryTable(context);
|
|
275
|
+
const product = buildProductTable(context, { category });
|
|
276
|
+
|
|
277
|
+
// Type error if any required table is missing
|
|
278
|
+
return { product, category } satisfies TablesFromNames<typeof tableNames>;
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
src/tailordb/
|
|
284
|
+
index.ts # Exports tableNames and default factory
|
|
285
|
+
product.ts # Table builder (not directly exported)
|
|
286
|
+
category.ts # Table builder (not directly exported)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Resolvers/Executors**: Must be exported as **separate files**. Each resolver or executor must be in its own file and cannot be barrel-exported from an `index.ts`:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
src/resolvers/
|
|
293
|
+
getProduct.ts # Each resolver in its own file
|
|
294
|
+
listProducts.ts
|
|
295
|
+
src/executors/
|
|
296
|
+
onProductCreated.ts # Each executor in its own file
|
|
297
|
+
onOrderPlaced.ts
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
This is because the Tailor SDK processes resolver and executor files individually, expecting each file to contain a single definition with a default export.
|
|
301
|
+
|
|
302
|
+
## 5. Set Up tailor.config.ts for Module Development
|
|
303
|
+
|
|
304
|
+
Every module needs a `tailor.config.ts` file for development. This file uses `loadModuleForDev` to resolve the module's factory functions into actual TailorDB table definitions, enabling the Tailor SDK's generator to produce Kysely type definitions.
|
|
305
|
+
|
|
306
|
+
### Why tailor.config.ts is Required
|
|
307
|
+
|
|
308
|
+
Omakase Modules use a factory pattern—tables are not defined directly but through `withModuleConfiguration` functions that receive configuration at runtime. The Tailor SDK's `@tailor-platform/kysely-type` generator needs concrete table definitions to generate TypeScript types.
|
|
309
|
+
|
|
310
|
+
`loadModuleForDev` bridges this gap by:
|
|
311
|
+
1. Resolving your module's factory functions with the `devConfig` you defined
|
|
312
|
+
2. Automatically registering any dependency modules
|
|
313
|
+
3. Producing actual TailorDB table definitions that the generator can process
|
|
314
|
+
|
|
315
|
+
The generated Kysely types (`src/generated/tailordb.ts`) enable:
|
|
316
|
+
- **Type-safe SQL queries** in your module's executors and resolvers
|
|
317
|
+
- **Cross-module JOIN queries** when other modules depend on yours via the `getDB` function
|
|
318
|
+
|
|
319
|
+
### Basic tailor.config.ts
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// tailor.config.ts
|
|
323
|
+
import { defineConfig, defineGenerators } from "@tailor-platform/sdk";
|
|
324
|
+
import {
|
|
325
|
+
loadModuleForDev,
|
|
326
|
+
getModulesReference,
|
|
327
|
+
} from "@izumisy-tailor/omakase-modules/config/sdk";
|
|
328
|
+
import myModule from "./src/module";
|
|
329
|
+
|
|
330
|
+
const moduleReference = await getModulesReference(
|
|
331
|
+
loadModuleForDev(myModule)
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
export default defineConfig({
|
|
335
|
+
name: "my-module",
|
|
336
|
+
|
|
337
|
+
db: {
|
|
338
|
+
"main-db": {
|
|
339
|
+
files: [...moduleReference.tailordb],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
export const generators = defineGenerators([
|
|
345
|
+
"@tailor-platform/kysely-type",
|
|
346
|
+
{ distPath: "./src/generated/tailordb.ts" },
|
|
347
|
+
]);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
> **Note**: Resolvers and executors are not required in `tailor.config.ts` during module development. The primary purpose is to generate Kysely type definitions for type-safe database queries.
|
|
351
|
+
|
|
352
|
+
### The Role of devConfig
|
|
353
|
+
|
|
354
|
+
The `devConfig` you define in your module with `.withDevConfig()` is the default configuration used by `loadModuleForDev`. This allows the generator to produce type definitions without explicitly passing configuration each time.
|
|
355
|
+
|
|
356
|
+
If your module doesn't require any configuration, you can omit `.withDevConfig()` entirely—`loadModuleForDev` will use an empty object `{}` as the default.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// src/module.ts - module with configuration
|
|
360
|
+
export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
|
|
361
|
+
packageName: pkg.name,
|
|
362
|
+
})
|
|
363
|
+
.withDevConfig({
|
|
364
|
+
// Default configuration for development
|
|
365
|
+
dataModel: {
|
|
366
|
+
product: { docNumberPrefix: "DEV" },
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
.build();
|
|
370
|
+
|
|
371
|
+
// src/module.ts - module without configuration (withDevConfig can be omitted)
|
|
372
|
+
export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
|
|
373
|
+
packageName: pkg.name,
|
|
374
|
+
}).build();
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
When `loadModuleForDev(myModule)` is called without a second argument, it uses this `devConfig` (or `{}` if not defined). You can override it by passing a custom config:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Use custom config instead of devConfig
|
|
381
|
+
loadModuleForDev(myModule, {
|
|
382
|
+
dataModel: {
|
|
383
|
+
product: { docNumberPrefix: "TEST" },
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
This flexibility is useful for:
|
|
389
|
+
- **Multiple tailor.config.ts files**: When you need different configurations for different environments (e.g., testing vs development)
|
|
390
|
+
- **Temporary overrides**: When you want to test with a different configuration without modifying `devConfig`
|
|
391
|
+
- **CI/CD pipelines**: When configuration needs to be passed dynamically from environment variables
|
|
392
|
+
|
|
393
|
+
### Handling Dependencies in Development
|
|
394
|
+
|
|
395
|
+
When your module has dependencies declared with `.withDependencies()`, `loadModuleForDev` automatically registers all dependency modules with empty configurations. This allows the generator to produce types that include dependency tables, enabling JOIN queries across module boundaries.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Module with dependencies
|
|
399
|
+
export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
|
|
400
|
+
packageName: pkg.name,
|
|
401
|
+
})
|
|
402
|
+
.withDependencies({ commerce: commerceModule })
|
|
403
|
+
.withDevConfig({ /* ... */ })
|
|
404
|
+
.build();
|
|
405
|
+
|
|
406
|
+
// In tailor.config.ts - dependencies are auto-registered
|
|
407
|
+
const moduleReference = await getModulesReference(
|
|
408
|
+
loadModuleForDev(myModule) // commerceModule is automatically loaded with {}
|
|
409
|
+
);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
> **Note**: In development context, dependency modules are registered with empty configurations (`{}`). Actual configuration for dependencies is provided when your module is used in an application via `loadModules`.
|
|
413
|
+
|
|
414
|
+
## 6. Configure Package Exports
|
|
415
|
+
|
|
416
|
+
Set up your `package.json` to export the module files.
|
|
417
|
+
|
|
418
|
+
> **Important**: The export paths `./backend/tailordb`, `./backend/resolvers/*`, and `./backend/executors/*` are **required** and must not be changed. The module system expects these exact paths to locate module files.
|
|
419
|
+
|
|
420
|
+
```json
|
|
421
|
+
{
|
|
422
|
+
"name": "my-module",
|
|
423
|
+
"type": "module",
|
|
424
|
+
"exports": {
|
|
425
|
+
".": "./src/module.ts",
|
|
426
|
+
"./db": "./src/db.ts",
|
|
427
|
+
"./backend/tailordb": "./src/tailordb/index.ts",
|
|
428
|
+
"./backend/resolvers/*": "./src/resolvers/*.ts",
|
|
429
|
+
"./backend/executors/*": "./src/executors/*.ts"
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Note that TailorDB uses an `index.ts` barrel export, while resolvers and executors use wildcard patterns to export individual files.
|
|
435
|
+
|
|
436
|
+
> **Design Note**: Modules always export TypeScript source files directly—there is no need to build or bundle them. The Tailor SDK handles TypeScript compilation and bundling as part of its deployment process, so providing raw `.ts` files eliminates unnecessary build steps and keeps the module development workflow simple.
|
|
437
|
+
|
|
438
|
+
## Best Practices
|
|
439
|
+
|
|
440
|
+
### Use Factory Functions for Tables
|
|
441
|
+
|
|
442
|
+
Always use factory functions (like `buildProductTable`) instead of directly exporting table definitions. This allows configuration to be injected.
|
|
443
|
+
|
|
444
|
+
### Type Your Tables
|
|
445
|
+
|
|
446
|
+
Use `satisfies` with `TablesFromNames` to ensure your returned tables match the expected type:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
return { product, category } satisfies TablesFromNames<typeof tableNames>;
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Provide Sensible Defaults
|
|
453
|
+
|
|
454
|
+
Always provide default values for optional configuration:
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
const prefix = config.dataModel?.product?.docNumberPrefix ?? "PROD";
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Utility Types Reference
|
|
461
|
+
|
|
462
|
+
The `@izumisy-tailor/omakase-modules/builder` package provides these utility types:
|
|
463
|
+
|
|
464
|
+
| Type | Description |
|
|
465
|
+
|------|-------------|
|
|
466
|
+
| `TablesFromNames<T>` | Derives a tables type from a `tableNames` array. `T` should be `typeof tableNames`. |
|
|
467
|
+
| `ModuleFactoryContext<C>` | Context passed to table builder functions. `C` is your module's config type. |
|
|
468
|
+
|
|
469
|
+
## Next Steps
|
|
470
|
+
|
|
471
|
+
- See [Using Modules](./using-modules.md) for how applications consume your module
|
|
@@ -39,21 +39,25 @@ export default loadModules((loader) => {
|
|
|
39
39
|
|
|
40
40
|
Each module exposes a `configure()` method that accepts a type-safe configuration object. The available options are defined by the module author.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
> **Note**: The `loadModules` function registers all configured modules in a global registry, which is later used by `getModulesReference` to generate wrapper files.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
## 2. Configure TypeScript Path Alias
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
You must configure a path alias in your `tsconfig.json` to point to the `modules.ts` file you created above. This alias is required for the generated wrapper files to correctly import your module configuration:
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
// tsconfig.json
|
|
47
50
|
{
|
|
48
51
|
"compilerOptions": {
|
|
52
|
+
// ... other options
|
|
49
53
|
"paths": {
|
|
50
|
-
"@
|
|
54
|
+
"@omakase-modules/config": ["./modules"]
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
```
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
The `@omakase-modules/config` alias should point to your `modules.ts` file (without the `.ts` extension). The wrapper files generated by `getModulesReference` use this alias to import the configured modules.
|
|
57
61
|
|
|
58
62
|
## 3. Reference Modules in tailor.config.ts
|
|
59
63
|
|
|
@@ -65,7 +69,7 @@ import { defineConfig } from "@tailor-platform/sdk";
|
|
|
65
69
|
import { getModulesReference } from "@izumisy-tailor/omakase-modules/config/sdk";
|
|
66
70
|
import modules from "./modules";
|
|
67
71
|
|
|
68
|
-
const moduleReference = getModulesReference(modules);
|
|
72
|
+
const moduleReference = await getModulesReference(modules);
|
|
69
73
|
|
|
70
74
|
export default defineConfig({
|
|
71
75
|
name: "my-app",
|
|
@@ -85,33 +89,51 @@ export default defineConfig({
|
|
|
85
89
|
});
|
|
86
90
|
```
|
|
87
91
|
|
|
92
|
+
`getModulesReference` generates wrapper files in `.tailor-sdk/.omakase` that properly inject module configurations and create named exports for each table. Make sure to add this directory to your `.gitignore`.
|
|
93
|
+
|
|
94
|
+
### Generated Wrapper Files
|
|
95
|
+
|
|
96
|
+
The wrapper generator creates files that:
|
|
97
|
+
1. Import the factory function from each module
|
|
98
|
+
2. Call it with the loaded modules from your `modules.ts`
|
|
99
|
+
3. Export the results (tables, resolvers, executors) for use by Tailor SDK
|
|
100
|
+
|
|
88
101
|
## 4. Handle Module Dependencies
|
|
89
102
|
|
|
90
|
-
When modules depend on other modules, you need to wire them together:
|
|
103
|
+
When modules depend on other modules, you need to wire them together. The `loader.add()` method returns the configured module, which can then be passed as a dependency to other modules via the `dependencies` property:
|
|
91
104
|
|
|
92
105
|
```typescript
|
|
93
106
|
// modules.ts
|
|
94
107
|
import { loadModules } from "@izumisy-tailor/omakase-modules";
|
|
108
|
+
import { db } from "@tailor-platform/sdk";
|
|
95
109
|
import ecommerceCoreModule from "omakase-module-commerce-core";
|
|
96
110
|
import orderModule from "omakase-module-order";
|
|
97
111
|
import inventoryModule from "omakase-module-inventory";
|
|
98
112
|
|
|
99
113
|
export default loadModules((loader) => {
|
|
100
114
|
// Add the base module first
|
|
101
|
-
const $
|
|
115
|
+
const $ecommerceModule = loader.add(
|
|
102
116
|
ecommerceCoreModule.configure({
|
|
103
|
-
config: {
|
|
117
|
+
config: {
|
|
118
|
+
dataModel: {
|
|
119
|
+
product: {
|
|
120
|
+
docNumberPrefix: "PP-PROD",
|
|
121
|
+
customAttributes: {
|
|
122
|
+
customStatus: db.enum(["new", "used", "refurbished"]),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
104
127
|
})
|
|
105
128
|
);
|
|
106
129
|
|
|
107
|
-
// Pass the commerce module as a dependency
|
|
108
|
-
const $
|
|
130
|
+
// Pass the commerce module as a dependency via the dependencies property
|
|
131
|
+
const $orderModule = loader.add(
|
|
109
132
|
orderModule.configure({
|
|
110
133
|
config: {
|
|
111
|
-
|
|
112
|
-
commerce: $commerce,
|
|
113
|
-
},
|
|
134
|
+
dataModel: {},
|
|
114
135
|
},
|
|
136
|
+
dependencies: { commerce: $ecommerceModule },
|
|
115
137
|
})
|
|
116
138
|
);
|
|
117
139
|
|
|
@@ -119,12 +141,10 @@ export default loadModules((loader) => {
|
|
|
119
141
|
loader.add(
|
|
120
142
|
inventoryModule.configure({
|
|
121
143
|
config: {
|
|
122
|
-
dbNamespace: "main",
|
|
123
|
-
|
|
124
|
-
commerce: $commerce,
|
|
125
|
-
order: $order,
|
|
126
|
-
},
|
|
144
|
+
dbNamespace: "main-db",
|
|
145
|
+
invantoryBootstrapBaseValue: 300,
|
|
127
146
|
},
|
|
147
|
+
dependencies: { commerce: $ecommerceModule, order: $orderModule },
|
|
128
148
|
})
|
|
129
149
|
);
|
|
130
150
|
|
|
@@ -132,7 +152,7 @@ export default loadModules((loader) => {
|
|
|
132
152
|
});
|
|
133
153
|
```
|
|
134
154
|
|
|
135
|
-
|
|
155
|
+
Note that dependencies are passed as a separate `dependencies` property alongside `config`, not nested inside `config`. This ensures type-safe dependency wiring.
|
|
136
156
|
|
|
137
157
|
## Common Configuration Options
|
|
138
158
|
|
|
@@ -169,6 +189,35 @@ config: {
|
|
|
169
189
|
}
|
|
170
190
|
```
|
|
171
191
|
|
|
192
|
+
### Module-Specific Options
|
|
193
|
+
|
|
194
|
+
Some modules have their own configuration options:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Inventory module example
|
|
198
|
+
inventoryModule.configure({
|
|
199
|
+
config: {
|
|
200
|
+
dbNamespace: "main", // Required: TailorDB namespace for this module
|
|
201
|
+
invantoryBootstrapBaseValue: 200, // Module-specific option
|
|
202
|
+
},
|
|
203
|
+
dependencies: { commerce: $ecommerceModule, order: $orderModule },
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Troubleshooting
|
|
208
|
+
|
|
209
|
+
### Module Not Found
|
|
210
|
+
|
|
211
|
+
If you see errors about modules not being found, ensure:
|
|
212
|
+
1. The module is installed as a dependency in your `package.json`
|
|
213
|
+
2. The module is added via `loader.add()` in your `modules.ts`
|
|
214
|
+
|
|
215
|
+
### Type Errors with Dependencies
|
|
216
|
+
|
|
217
|
+
If you see type errors when passing dependencies:
|
|
218
|
+
1. Ensure the dependency module is added before the dependent module
|
|
219
|
+
2. Use the return value from `loader.add()` as the dependency value
|
|
220
|
+
|
|
172
221
|
## Next Steps
|
|
173
222
|
|
|
174
223
|
- See [Creating Modules](./creating-modules.md) if you want to build your own reusable modules
|