@izumisy-tailor/omakase-modules 0.1.0 → 0.3.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 ADDED
@@ -0,0 +1,47 @@
1
+ # Omakase Modules
2
+
3
+ A **configurable module system** for Tailor Platform. Define reusable modules with type-safe configurations and share them across applications.
4
+
5
+ ## Motivation
6
+
7
+ When building applications on Tailor Platform, you often face these challenges:
8
+
9
+ 1. **Reusability**: You want to reuse common data models and business logic (e.g., e-commerce, inventory, orders) across multiple applications
10
+ 2. **Customizability**: While modules should be reusable, you need to customize them for application-specific requirements
11
+ 3. **Dependency Management**: Module dependencies should be explicitly defined and managed in a type-safe manner
12
+
13
+ Omakase Modules is designed to solve these challenges by providing a flexible, type-safe module system.
14
+
15
+ ## Key Features
16
+
17
+ - **Module Definition**: Define modules with `defineModule` and declare type-safe configuration schemas
18
+ - **Module Configuration**: Configure modules at the application level, injecting custom attributes and prefixes
19
+ - **Dependency Management**: Explicitly define inter-module dependencies with the `ModuleDependency` type
20
+ - **Configuration Injection**: Inject configurations into TailorDB, Resolvers, and Executors using `withModuleConfiguration`
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pnpm add @izumisy-tailor/omakase-modules
26
+ ```
27
+
28
+ ## Documentation
29
+
30
+ - [Using Modules](./docs/tutorials/using-modules.md) - How to use pre-built modules in your application
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`
@@ -0,0 +1,296 @@
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
+ ## 1. Define the Module
26
+
27
+ Create the module definition with `defineModule`:
28
+
29
+ ```typescript
30
+ // src/module.ts
31
+ import {
32
+ defineModule,
33
+ type TablesFromNames,
34
+ } from "@izumisy-tailor/omakase-modules/builder";
35
+ import type { ModuleConfig } from "./types";
36
+ import { tableNames } from "./tailordb";
37
+ import * as pkg from "../package.json";
38
+
39
+ export default defineModule<ModuleConfig, TablesFromNames<typeof tableNames>>({
40
+ packageName: pkg.name,
41
+ });
42
+ ```
43
+
44
+ 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.
45
+
46
+ ## 2. Define Configuration Types
47
+
48
+ Define what configuration options your module accepts:
49
+
50
+ ```typescript
51
+ // src/types.ts
52
+ import { TailorField } from "@tailor-platform/sdk";
53
+
54
+ type DataModelConfiguration = {
55
+ docNumberPrefix?: string;
56
+ customAttributes?: Record<string, TailorField<any>>;
57
+ };
58
+
59
+ export type ModuleConfig = {
60
+ dataModel?: {
61
+ product?: DataModelConfiguration;
62
+ category?: DataModelConfiguration;
63
+ };
64
+ };
65
+ ```
66
+
67
+ ## 3. Build Tables with Configuration
68
+
69
+ Use `withModuleConfiguration` to access configuration when building tables:
70
+
71
+ ```typescript
72
+ // src/tailordb/index.ts
73
+ import {
74
+ withModuleConfiguration,
75
+ type TablesFromNames,
76
+ } from "@izumisy-tailor/omakase-modules/builder";
77
+ import moduleDef from "../module";
78
+ import { buildProductTable } from "./product";
79
+ import { buildCategoryTable } from "./category";
80
+
81
+ /**
82
+ * List of table names exported by this module.
83
+ * This is the single source of truth for table definitions.
84
+ */
85
+ export const tableNames = ["product", "category"] as const;
86
+
87
+ /**
88
+ * Factory function that creates tailordb tables with the given module configuration.
89
+ * This is called by the wrapper files generated by getModulesReference.
90
+ */
91
+ export default withModuleConfiguration(moduleDef, (context) => {
92
+ const category = buildCategoryTable(context);
93
+ const product = buildProductTable(context, { category });
94
+
95
+ return { product, category } satisfies TablesFromNames<typeof tableNames>;
96
+ });
97
+ ```
98
+
99
+ > **Important**: The `tableNames` export is required for the wrapper generator to create named exports for each table.
100
+
101
+ ```typescript
102
+ // src/tailordb/product.ts
103
+ import { type ModuleFactoryContext } from "@izumisy-tailor/omakase-modules/builder";
104
+ import { db, type TailorDBType } from "@tailor-platform/sdk";
105
+ import type { ModuleConfig } from "../types";
106
+
107
+ export const buildProductTable = (
108
+ { config }: ModuleFactoryContext<ModuleConfig>,
109
+ deps: { category: TailorDBType }
110
+ ) => {
111
+ const customAttributes = config.dataModel?.product?.customAttributes || {};
112
+ const docNumberPrefix = config.dataModel?.product?.docNumberPrefix ?? "PROD";
113
+
114
+ return db.type("Product", {
115
+ name: db.string(),
116
+ sku: db.string().unique(),
117
+ categoryId: db.uuid({ optional: true }).relation({
118
+ type: "keyOnly",
119
+ toward: { type: deps.category },
120
+ }),
121
+ docNumber: db.docNumber({ prefix: docNumberPrefix }),
122
+ ...customAttributes,
123
+ ...db.fields.timestamps(),
124
+ });
125
+ };
126
+ ```
127
+
128
+ ## 4. Declare Dependencies on Other Modules
129
+
130
+ If your module depends on other modules, use `ModuleDependency`:
131
+
132
+ ```typescript
133
+ // src/types.ts
134
+ import type { ModuleDependency } from "@izumisy-tailor/omakase-modules/builder";
135
+ import type commerceCoreModule from "omakase-module-commerce-core";
136
+
137
+ export type ModuleConfig = {
138
+ dataModel?: { /* ... */ };
139
+ dependencies: {
140
+ commerce: ModuleDependency<typeof commerceCoreModule>;
141
+ };
142
+ };
143
+ ```
144
+
145
+ This ensures type-safe dependency injection when users configure your module.
146
+
147
+ ### Using Dependency Tables in TailorDB
148
+
149
+ When your module needs to reference tables from dependency modules, use `loadedModules.getTables()` in an async factory function:
150
+
151
+ ```typescript
152
+ // src/tailordb/index.ts
153
+ import { withModuleConfiguration } from "@izumisy-tailor/omakase-modules/builder";
154
+ import moduleDef from "../module";
155
+ import commerceModuleTables from "omakase-module-commerce-core/backend/tailordb";
156
+
157
+ export default withModuleConfiguration(
158
+ moduleDef,
159
+ async (context, loadedModules) => {
160
+ // Get tables from dependency modules
161
+ const { product, productVariant } = await loadedModules.getTables(
162
+ commerceModuleTables
163
+ );
164
+
165
+ const inventory = buildInventoryTable(context, { product, productVariant });
166
+ // ...
167
+ }
168
+ );
169
+ ```
170
+
171
+ ### Using Dependency Tables in Executors
172
+
173
+ Executors can also access dependency tables:
174
+
175
+ ```typescript
176
+ // src/executors/myExecutor.ts
177
+ import { withModuleConfiguration } from "@izumisy-tailor/omakase-modules/builder";
178
+ import { createExecutor, recordCreatedTrigger } from "@tailor-platform/sdk";
179
+ import commerceModuleTables from "omakase-module-commerce-core/backend/tailordb";
180
+ import moduleDef from "../module";
181
+
182
+ export default withModuleConfiguration(
183
+ moduleDef,
184
+ async (_context, loadedModules) => {
185
+ const { productVariant } = await loadedModules.getTables(
186
+ commerceModuleTables
187
+ );
188
+
189
+ return createExecutor({
190
+ name: "my-executor",
191
+ trigger: recordCreatedTrigger({ type: productVariant }),
192
+ operation: { /* ... */ },
193
+ });
194
+ }
195
+ );
196
+ ```
197
+
198
+ ## File Export Requirements
199
+
200
+ Due to Tailor SDK API requirements, **TailorDB** and **Resolvers/Executors** have different export patterns:
201
+
202
+ **TailorDB**: Should use a single `index.ts` barrel export with a `tableNames` constant and a default factory function. This is **strongly recommended** because:
203
+ 1. It allows you to use `satisfies` to verify that all required tables are defined
204
+ 2. The `tableNames` export enables the wrapper generator to create named exports
205
+
206
+ ```typescript
207
+ // src/tailordb/index.ts
208
+ export const tableNames = ["product", "category"] as const;
209
+
210
+ export default withModuleConfiguration(moduleDef, (context) => {
211
+ const category = buildCategoryTable(context);
212
+ const product = buildProductTable(context, { category });
213
+
214
+ // Type error if any required table is missing
215
+ return { product, category } satisfies TablesFromNames<typeof tableNames>;
216
+ });
217
+ ```
218
+
219
+ ```
220
+ src/tailordb/
221
+ index.ts # Exports tableNames and default factory
222
+ product.ts # Table builder (not directly exported)
223
+ category.ts # Table builder (not directly exported)
224
+ ```
225
+
226
+ **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`:
227
+
228
+ ```
229
+ src/resolvers/
230
+ getProduct.ts # Each resolver in its own file
231
+ listProducts.ts
232
+ src/executors/
233
+ onProductCreated.ts # Each executor in its own file
234
+ onOrderPlaced.ts
235
+ ```
236
+
237
+ This is because the Tailor SDK processes resolver and executor files individually, expecting each file to contain a single definition with a default export.
238
+
239
+ ## 5. Configure Package Exports
240
+
241
+ Set up your `package.json` to export the module files.
242
+
243
+ > **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.
244
+
245
+ ```json
246
+ {
247
+ "name": "my-module",
248
+ "type": "module",
249
+ "exports": {
250
+ ".": "./src/module.ts",
251
+ "./backend/tailordb": "./src/tailordb/index.ts",
252
+ "./backend/resolvers/*": "./src/resolvers/*.ts",
253
+ "./backend/executors/*": "./src/executors/*.ts"
254
+ }
255
+ }
256
+ ```
257
+
258
+ Note that TailorDB uses an `index.ts` barrel export, while resolvers and executors use wildcard patterns to export individual files.
259
+
260
+ > **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.
261
+
262
+ ## Best Practices
263
+
264
+ ### Use Factory Functions for Tables
265
+
266
+ Always use factory functions (like `buildProductTable`) instead of directly exporting table definitions. This allows configuration to be injected.
267
+
268
+ ### Type Your Tables
269
+
270
+ Use `satisfies` with `TablesFromNames` to ensure your returned tables match the expected type:
271
+
272
+ ```typescript
273
+ return { product, category } satisfies TablesFromNames<typeof tableNames>;
274
+ ```
275
+
276
+ ### Provide Sensible Defaults
277
+
278
+ Always provide default values for optional configuration:
279
+
280
+ ```typescript
281
+ const prefix = config.dataModel?.product?.docNumberPrefix ?? "PROD";
282
+ ```
283
+
284
+ ## Utility Types Reference
285
+
286
+ The `@izumisy-tailor/omakase-modules/builder` package provides these utility types:
287
+
288
+ | Type | Description |
289
+ |------|-------------|
290
+ | `TablesFromNames<T>` | Derives a tables type from a `tableNames` array. `T` should be `typeof tableNames`. |
291
+ | `ModuleFactoryContext<C>` | Context passed to table builder functions. `C` is your module's config type. |
292
+ | `ModuleDependency<T>` | Type for declaring dependencies on other modules. `T` is the module's default export type. |
293
+
294
+ ## Next Steps
295
+
296
+ - See [Using Modules](./using-modules.md) for how applications consume your module
@@ -0,0 +1,224 @@
1
+ # Using Modules in Your Application
2
+
3
+ This guide explains how to use pre-built Omakase Modules in your Tailor Platform application.
4
+
5
+ ## Prerequisites
6
+
7
+ - A Tailor Platform application
8
+ - Modules installed as dependencies (e.g., `omakase-module-commerce-core`)
9
+
10
+ ## 1. Configure Modules
11
+
12
+ Use `loadModules` to configure the modules your application needs:
13
+
14
+ ```typescript
15
+ // modules.ts
16
+ import { loadModules } from "@izumisy-tailor/omakase-modules";
17
+ import { db } from "@tailor-platform/sdk";
18
+ import ecommerceCoreModule from "omakase-module-commerce-core";
19
+
20
+ export default loadModules((loader) => {
21
+ loader.add(
22
+ ecommerceCoreModule.configure({
23
+ config: {
24
+ dataModel: {
25
+ product: {
26
+ docNumberPrefix: "MY-PROD",
27
+ customAttributes: {
28
+ customStatus: db.enum(["new", "used", "refurbished"]),
29
+ },
30
+ },
31
+ },
32
+ },
33
+ })
34
+ );
35
+
36
+ return loader;
37
+ });
38
+ ```
39
+
40
+ Each module exposes a `configure()` method that accepts a type-safe configuration object. The available options are defined by the module author.
41
+
42
+ > **Note**: The `loadModules` function registers all configured modules in a global registry, which is later used by `getModulesReference` to generate wrapper files.
43
+
44
+ ## 2. Configure TypeScript Path Alias
45
+
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
50
+ {
51
+ "compilerOptions": {
52
+ // ... other options
53
+ "paths": {
54
+ "@omakase-modules/config": ["./modules"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
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.
61
+
62
+ ## 3. Reference Modules in tailor.config.ts
63
+
64
+ Use `getModulesReference` to include module files in your Tailor configuration:
65
+
66
+ ```typescript
67
+ // tailor.config.ts
68
+ import { defineConfig } from "@tailor-platform/sdk";
69
+ import { getModulesReference } from "@izumisy-tailor/omakase-modules/config/sdk";
70
+ import modules from "./modules";
71
+
72
+ const moduleReference = await getModulesReference(modules);
73
+
74
+ export default defineConfig({
75
+ name: "my-app",
76
+ db: {
77
+ main: {
78
+ files: ["./src/tailordb/*.ts", ...moduleReference.tailordb],
79
+ },
80
+ },
81
+ resolver: {
82
+ "main-pipeline": {
83
+ files: ["./src/resolvers/*.ts", ...moduleReference.resolver],
84
+ },
85
+ },
86
+ executor: {
87
+ files: ["./src/executors/**/*.ts", ...moduleReference.executor],
88
+ },
89
+ });
90
+ ```
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
+
101
+ ## 4. Handle Module Dependencies
102
+
103
+ When modules depend on other modules, you need to wire them together:
104
+
105
+ ```typescript
106
+ // modules.ts
107
+ import { loadModules } from "@izumisy-tailor/omakase-modules";
108
+ import { db } from "@tailor-platform/sdk";
109
+ import ecommerceCoreModule from "omakase-module-commerce-core";
110
+ import orderModule from "omakase-module-order";
111
+ import inventoryModule from "omakase-module-inventory";
112
+
113
+ export default loadModules((loader) => {
114
+ // Add the base module first
115
+ const $commerce = loader.add(
116
+ ecommerceCoreModule.configure({
117
+ config: {
118
+ dataModel: {
119
+ product: {
120
+ docNumberPrefix: "PP-PROD",
121
+ customAttributes: {
122
+ customStatus: db.enum(["new", "used", "refurbished"]),
123
+ },
124
+ },
125
+ },
126
+ },
127
+ })
128
+ );
129
+
130
+ // Pass the commerce module as a dependency
131
+ const $order = loader.add(
132
+ orderModule.configure({
133
+ config: {
134
+ dependencies: {
135
+ commerce: $commerce,
136
+ },
137
+ },
138
+ })
139
+ );
140
+
141
+ // Inventory depends on both commerce and order
142
+ loader.add(
143
+ inventoryModule.configure({
144
+ config: {
145
+ dbNamespace: "main",
146
+ dependencies: {
147
+ commerce: $commerce,
148
+ order: $order,
149
+ },
150
+ },
151
+ })
152
+ );
153
+
154
+ return loader;
155
+ });
156
+ ```
157
+
158
+ The `loader.add()` method returns the configured module, which can then be passed as a dependency to other modules. This ensures type-safe dependency wiring.
159
+
160
+ ## Common Configuration Options
161
+
162
+ Most modules support these common configuration patterns:
163
+
164
+ ### Custom Attributes
165
+
166
+ Add custom fields to module tables:
167
+
168
+ ```typescript
169
+ config: {
170
+ dataModel: {
171
+ product: {
172
+ customAttributes: {
173
+ myCustomField: db.string(),
174
+ priority: db.int(),
175
+ },
176
+ },
177
+ },
178
+ }
179
+ ```
180
+
181
+ ### Document Number Prefixes
182
+
183
+ Customize document number prefixes:
184
+
185
+ ```typescript
186
+ config: {
187
+ dataModel: {
188
+ order: {
189
+ docNumberPrefix: "ORD-2024",
190
+ },
191
+ },
192
+ }
193
+ ```
194
+
195
+ ### Module-Specific Options
196
+
197
+ Some modules have their own configuration options:
198
+
199
+ ```typescript
200
+ // Inventory module example
201
+ config: {
202
+ dbNamespace: "main", // Required: TailorDB namespace for this module
203
+ invantoryBootstrapBaseValue: 200, // Module-specific option
204
+ dependencies: { /* ... */ },
205
+ }
206
+ ```
207
+
208
+ ## Troubleshooting
209
+
210
+ ### Module Not Found
211
+
212
+ If you see errors about modules not being found, ensure:
213
+ 1. The module is installed as a dependency in your `package.json`
214
+ 2. The module is added via `loader.add()` in your `modules.ts`
215
+
216
+ ### Type Errors with Dependencies
217
+
218
+ If you see type errors when passing dependencies:
219
+ 1. Ensure the dependency module is added before the dependent module
220
+ 2. Use the return value from `loader.add()` as the dependency value
221
+
222
+ ## Next Steps
223
+
224
+ - See [Creating Modules](./creating-modules.md) if you want to build your own reusable modules
package/package.json CHANGED
@@ -1,34 +1,17 @@
1
1
  {
2
2
  "name": "@izumisy-tailor/omakase-modules",
3
3
  "private": false,
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "description": "Modularization mechanism for Tailor Platform application powered by Tailor SDK",
6
6
  "type": "module",
7
7
  "files": [
8
- "dist"
8
+ "src",
9
+ "docs"
9
10
  ],
10
11
  "exports": {
11
- ".": {
12
- "default": "./dist/config/index.mjs",
13
- "types": "./dist/config/index.d.mts"
14
- },
15
- "./config/sdk": {
16
- "default": "./dist/config/sdk/index.mjs",
17
- "types": "./dist/config/sdk/index.d.mts"
18
- },
19
- "./builder": {
20
- "default": "./dist/builder/index.mjs",
21
- "types": "./dist/builder/index.d.mts"
22
- },
23
- "./config/loader": {
24
- "default": "./dist/stub-loader/index.mjs",
25
- "types": "./dist/stub-loader/index.d.mts"
26
- }
27
- },
28
- "scripts": {
29
- "build": "tsdown",
30
- "dev": "tsdown --watch",
31
- "type-check": "tsc"
12
+ ".": "./src/config/index.ts",
13
+ "./config/sdk": "./src/config/sdk/index.ts",
14
+ "./builder": "./src/builder/index.ts"
32
15
  },
33
16
  "keywords": [
34
17
  "tailor-platform",
@@ -38,13 +21,19 @@
38
21
  ],
39
22
  "author": "Tailor Inc.",
40
23
  "license": "MIT",
41
- "packageManager": "pnpm@10.22.0",
42
24
  "devDependencies": {
43
25
  "@types/node": "^25.0.3",
44
- "tsdown": "^0.18.0",
45
- "typescript": "^5"
26
+ "typescript": "^5",
27
+ "vitest": "^4.0.16"
46
28
  },
47
29
  "peerDependencies": {
48
- "@tailor-platform/sdk": "^0.17.0"
30
+ "@tailor-platform/sdk": "^0.20.0"
31
+ },
32
+ "dependencies": {
33
+ "dedent": "^1.7.1"
34
+ },
35
+ "scripts": {
36
+ "type-check": "tsc",
37
+ "test": "vitest run"
49
38
  }
50
- }
39
+ }