@zaamx/netme-bundle 0.0.4 → 0.0.5

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 CHANGED
@@ -1,64 +1,459 @@
1
- <p align="center">
2
- <a href="https://www.medusajs.com">
3
- <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/59018053/229103275-b5e482bb-4601-46e6-8142-244f531cebdb.svg">
5
- <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
6
- <img alt="Medusa logo" src="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
7
- </picture>
8
- </a>
9
- </p>
10
- <h1 align="center">
11
- Medusa Plugin Starter
12
- </h1>
1
+ # @zaamx/netme-bundle
13
2
 
14
- <h4 align="center">
15
- <a href="https://docs.medusajs.com">Documentation</a> |
16
- <a href="https://www.medusajs.com">Website</a>
17
- </h4>
3
+ A Medusa v2 plugin for creating and managing **product bundles** — linking a parent product to one or more child products with configurable quantities, pricing rules, and metadata.
18
4
 
19
- <p align="center">
20
- Building blocks for digital commerce
21
- </p>
22
- <p align="center">
23
- <a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
24
- <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
25
- </a>
26
- <a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
27
- <a href="https://discord.gg/xpCwq3Kfn8">
28
- <img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
29
- </a>
30
- <a href="https://twitter.com/intent/follow?screen_name=medusajs">
31
- <img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
32
- </a>
33
- </p>
5
+ **Version**: `0.0.4` | **License**: MIT | **Requires**: Medusa `>= 2.4.0` | **Node**: `>= 20`
34
6
 
35
- ## Compatibility
7
+ ---
36
8
 
37
- This starter is compatible with versions >= 2.4.0 of `@medusajs/medusa`.
9
+ ## Table of Contents
38
10
 
39
- ## Getting Started
11
+ - [Overview](#overview)
12
+ - [Installation](#installation)
13
+ - [Registration](#registration)
14
+ - [Architecture](#architecture)
15
+ - [Data Model](#data-model)
16
+ - [Module](#module)
17
+ - [Module Link](#module-link)
18
+ - [Workflows](#workflows)
19
+ - [API Reference](#api-reference)
20
+ - [Admin Routes](#admin-routes)
21
+ - [Store Routes](#store-routes)
22
+ - [Admin UI](#admin-ui)
23
+ - [Types](#types)
24
+ - [Database Schema](#database-schema)
25
+ - [Build & Development](#build--development)
40
26
 
41
- Visit the [Quickstart Guide](https://docs.medusajs.com/learn/installation) to set up a server.
27
+ ---
42
28
 
43
- Visit the [Plugins documentation](https://docs.medusajs.com/learn/fundamentals/plugins) to learn more about plugins and how to create them.
29
+ ## Overview
44
30
 
45
- Visit the [Docs](https://docs.medusajs.com/learn/installation#get-started) to learn more about our system requirements.
31
+ `@zaamx/netme-bundle` adds full product bundle management to a Medusa v2 backend. It exposes:
46
32
 
47
- ## What is Medusa
33
+ - A **`bundle` module** with its own database table and service
34
+ - A **module link** connecting Medusa's `ProductModule` to the `bundle` module
35
+ - **Workflows** for creating, updating, and deleting bundles
36
+ - **Admin REST API** routes for CRUD operations
37
+ - **Store REST API** routes for storefront consumption and cart integration with dynamic pricing
38
+ - An **Admin UI** page with full CRUD, infinite scroll product selection, and metadata management
48
39
 
49
- Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.
40
+ ---
50
41
 
51
- Learn more about [Medusa’s architecture](https://docs.medusajs.com/learn/introduction/architecture) and [commerce modules](https://docs.medusajs.com/learn/fundamentals/modules/commerce-modules) in the Docs.
42
+ ## Installation
52
43
 
53
- ## Community & Contributions
44
+ ```bash
45
+ npm install @zaamx/netme-bundle
46
+ # or
47
+ yarn add @zaamx/netme-bundle
48
+ ```
54
49
 
55
- The community and core team are available in [GitHub Discussions](https://github.com/medusajs/medusa/discussions), where you can ask for support, discuss roadmap, and share ideas.
50
+ ---
56
51
 
57
- Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.
52
+ ## Registration
58
53
 
59
- ## Other channels
54
+ Add the plugin to your `medusa-config.ts`:
60
55
 
61
- - [GitHub Issues](https://github.com/medusajs/medusa/issues)
62
- - [Twitter](https://twitter.com/medusajs)
63
- - [LinkedIn](https://www.linkedin.com/company/medusajs)
64
- - [Medusa Blog](https://medusajs.com/blog/)
56
+ ```typescript
57
+ import { defineConfig } from "@medusajs/framework/utils"
58
+
59
+ export default defineConfig({
60
+ plugins: [
61
+ {
62
+ resolve: "@zaamx/netme-bundle",
63
+ options: {},
64
+ },
65
+ ],
66
+ // ...
67
+ })
68
+ ```
69
+
70
+ After registering, run database migrations:
71
+
72
+ ```bash
73
+ npx medusa db:migrate
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Architecture
79
+
80
+ ```
81
+ @zaamx/netme-bundle
82
+ ├── modules/bundles — Bundle data model, service, migrations
83
+ ├── links/product-bundle — Module link: Product ↔ Bundle
84
+ ├── workflows/ — update-product-bundle, delete-product-bundle
85
+ ├── api/admin/ — Admin REST routes
86
+ ├── api/store/ — Store REST routes
87
+ └── admin/ — Admin dashboard UI (routes, components)
88
+ ```
89
+
90
+ The plugin follows Medusa's modular architecture: the `bundle` module is isolated from the core `ProductModule` and connected via a module link. All write operations go through workflows that invoke the bundle service.
91
+
92
+ ---
93
+
94
+ ## Data Model
95
+
96
+ **File**: `src/modules/bundles/models/bundle.ts`
97
+
98
+ | Field | Type | Default | Description |
99
+ |---|---|---|---|
100
+ | `id` | `TEXT` | — | Primary key, matches the associated product ID |
101
+ | `child_products` | `JSONB` | `{"data":[]}` | Array of child product configurations |
102
+ | `bundle_meta` | `JSONB` | `{"data":[]}` | Array of arbitrary key-value metadata pairs |
103
+ | `is_bundle` | `BOOLEAN` | `false` | Whether this record represents an active bundle |
104
+ | `created_at` | `TIMESTAMPTZ` | `now()` | Creation timestamp |
105
+ | `updated_at` | `TIMESTAMPTZ` | `now()` | Last update timestamp |
106
+ | `deleted_at` | `TIMESTAMPTZ` | `NULL` | Soft delete timestamp |
107
+
108
+ An index `IDX_bundle_deleted_at` is created on `deleted_at` for efficient active-bundle queries.
109
+
110
+ ---
111
+
112
+ ## Module
113
+
114
+ **File**: `src/modules/bundles/index.ts`
115
+
116
+ ```typescript
117
+ export const BUNDLE_MODULE = "bundle"
118
+ export default Module(BUNDLE_MODULE, { service: BundleService })
119
+ ```
120
+
121
+ ### BundleService
122
+
123
+ Extends `MedusaService` with the `Bundle` model. Inherits CRUD methods:
124
+
125
+ | Method | Description |
126
+ |---|---|
127
+ | `listBundles(filters?, config?)` | List bundles with optional filters |
128
+ | `createBundles(data)` | Create one or more bundles |
129
+ | `updateBundles(data)` | Update one or more bundles |
130
+ | `deleteBundles(ids)` | Soft-delete bundles by ID |
131
+ | `attachBundleToProduct(productId)` | Retrieve bundle data for a product with `child_products` relation |
132
+
133
+ ---
134
+
135
+ ## Module Link
136
+
137
+ **File**: `src/links/product-bundle.ts`
138
+
139
+ ```typescript
140
+ export default defineLink(
141
+ ProductModule.linkable.product,
142
+ BundleModule.linkable.bundle
143
+ )
144
+ ```
145
+
146
+ This link associates each bundle record with a Medusa product while keeping both modules independent. It enables querying products with their bundle data through Medusa's query layer.
147
+
148
+ ---
149
+
150
+ ## Workflows
151
+
152
+ ### `updateProductBundle`
153
+
154
+ **File**: `src/workflows/update-product-bundle.ts`
155
+
156
+ Creates or updates a bundle for a given product.
157
+
158
+ **Input** (`UpdateProductBundleInput`):
159
+
160
+ ```typescript
161
+ {
162
+ product_id: string
163
+ is_bundle?: boolean
164
+ child_product_ids?: Array<{
165
+ id: string
166
+ min_quantity?: number
167
+ max_quantity?: number
168
+ default_quantity?: number
169
+ optional?: boolean
170
+ separate_shipping?: boolean
171
+ individual_price?: boolean
172
+ }>
173
+ bundle_meta?: Array<{
174
+ key: string
175
+ value: string
176
+ }>
177
+ }
178
+ ```
179
+
180
+ **Steps**:
181
+ 1. Resolve bundle and product services via `MedusaModules`
182
+ 2. Check if a bundle already exists for `product_id`
183
+ 3. If none exists and `is_bundle=true`: create a new bundle record
184
+ 4. If one exists: update with the provided fields
185
+ 5. Return the product with enriched bundle data
186
+
187
+ ---
188
+
189
+ ### `deleteProductBundle`
190
+
191
+ **File**: `src/workflows/delete-product-bundle.ts`
192
+
193
+ Removes the bundle association from a product.
194
+
195
+ **Input** (`DeleteProductBundleInput`):
196
+
197
+ ```typescript
198
+ {
199
+ product_id: string
200
+ }
201
+ ```
202
+
203
+ **Steps**:
204
+ 1. Find the bundle record linked to `product_id`
205
+ 2. Delete it if found
206
+ 3. Return the updated product
207
+
208
+ ---
209
+
210
+ ## API Reference
211
+
212
+ ### Admin Routes
213
+
214
+ All admin routes are authenticated and accessible under `/admin/`.
215
+
216
+ ---
217
+
218
+ #### `GET /admin/bundled-products`
219
+
220
+ Returns all bundled products with enriched child product data.
221
+
222
+ **Query params**: `limit`, `offset`
223
+
224
+ **Response**:
225
+ ```json
226
+ {
227
+ "bundled_products": [
228
+ {
229
+ "id": "bundle_01J...",
230
+ "title": "Product Title",
231
+ "product": { "id": "prod_01J..." },
232
+ "items": [
233
+ {
234
+ "id": "prod_child_01J...",
235
+ "product": { "id": "prod_01J...", "title": "Child Title" },
236
+ "quantity": 1,
237
+ "min_quantity": 1,
238
+ "max_quantity": 5,
239
+ "optional": false,
240
+ "separate_shipping": false,
241
+ "individual_price": false
242
+ }
243
+ ],
244
+ "bundle_meta": [{ "key": "promo", "value": "summer" }],
245
+ "is_bundle": true,
246
+ "created_at": "2025-01-01T00:00:00Z",
247
+ "updated_at": "2025-01-01T00:00:00Z"
248
+ }
249
+ ],
250
+ "count": 10,
251
+ "limit": 15,
252
+ "offset": 0
253
+ }
254
+ ```
255
+
256
+ ---
257
+
258
+ #### `POST /admin/bundled-products`
259
+
260
+ Creates a new bundled product.
261
+
262
+ **Body**: `UpdateProductBundleInput` (see [Workflows](#workflows))
263
+
264
+ ---
265
+
266
+ #### `GET /admin/products/:id/bundle`
267
+
268
+ Retrieves bundle data for a specific product.
269
+
270
+ **Response**: Bundle object for the product, or empty if none.
271
+
272
+ ---
273
+
274
+ #### `POST /admin/products/:id/bundle`
275
+
276
+ Creates or replaces the bundle for a product. Always deletes the existing bundle first, then applies the new configuration.
277
+
278
+ **Body**: `UpdateProductBundleInput`
279
+
280
+ ---
281
+
282
+ #### `DELETE /admin/products/:id/bundle`
283
+
284
+ Removes the bundle from a product.
285
+
286
+ ---
287
+
288
+ #### `GET /admin/plugin`
289
+
290
+ Health check. Returns HTTP 200.
291
+
292
+ ---
293
+
294
+ ### Store Routes
295
+
296
+ Unauthenticated routes for the storefront.
297
+
298
+ ---
299
+
300
+ #### `GET /store/products/:id/bundle`
301
+
302
+ Returns the bundle configuration for a product, with enriched child product data.
303
+
304
+ **Response**: Same structure as the admin single-product bundle endpoint.
305
+
306
+ ---
307
+
308
+ #### `POST /store/carts/:id/bundle-line-items`
309
+
310
+ Adds items to a cart with bundle-aware dynamic pricing.
311
+
312
+ **Body**:
313
+ ```typescript
314
+ {
315
+ items: Array<{
316
+ variant_id: string
317
+ quantity: number
318
+ unit_price?: number
319
+ metadata?: {
320
+ bundle_id?: string
321
+ bundled_by?: string // if set → price = 0.001 (free)
322
+ bundle_sticks_price?: {
323
+ nuevoPrecioPaquete: number // if set → use this price
324
+ }
325
+ price_rules?: {
326
+ nuevoPrecio: number // if set → use this price
327
+ }
328
+ }
329
+ }>
330
+ }
331
+ ```
332
+
333
+ **Pricing logic** (applied per item):
334
+
335
+ | Condition | Price Applied |
336
+ |---|---|
337
+ | `metadata.bundled_by` is set | `0.001` (effectively free child item) |
338
+ | `metadata.bundle_sticks_price` is set | `bundle_sticks_price.nuevoPrecioPaquete` |
339
+ | `metadata.price_rules` is set | `price_rules.nuevoPrecio` |
340
+ | None of the above | Default Medusa pricing |
341
+
342
+ ---
343
+
344
+ #### `GET /store/plugin`
345
+
346
+ Health check. Returns HTTP 200.
347
+
348
+ ---
349
+
350
+ ## Admin UI
351
+
352
+ The plugin registers a custom page in the Medusa Admin dashboard.
353
+
354
+ ### Bundled Products Page
355
+
356
+ **Route**: `/bundled-products` (sidebar entry with a cube icon, label "Bundled Products")
357
+
358
+ **Features**:
359
+
360
+ | Feature | Details |
361
+ |---|---|
362
+ | Bundle list | Table with columns: ID, Title, Items count, Product link, Status |
363
+ | Pagination | 15 items per page |
364
+ | Create bundle | Modal with product selector and item configuration |
365
+ | Edit bundle | Same modal, product selector disabled during edit |
366
+ | Delete bundle | Confirmation + API call |
367
+ | Infinite scroll | Product selector loads products incrementally via Intersection Observer |
368
+ | Bundle metadata | Key-value pairs, add/remove dynamically |
369
+
370
+ ### Per-Item Configuration (in Create/Edit modal)
371
+
372
+ Each child product entry in a bundle supports:
373
+
374
+ - Product selector (dropdown with search, infinite scroll)
375
+ - `min_quantity`, `max_quantity`, `default_quantity` number inputs
376
+ - Toggle: `optional`
377
+ - Toggle: `separate_shipping`
378
+ - Toggle: `individual_price`
379
+ - Remove button
380
+
381
+ ---
382
+
383
+ ## Types
384
+
385
+ ```typescript
386
+ // Child product configuration stored in bundle.child_products
387
+ type ChildProductConfig = {
388
+ id: string
389
+ min_quantity?: number
390
+ max_quantity?: number
391
+ default_quantity?: number
392
+ optional?: boolean
393
+ separate_shipping?: boolean
394
+ individual_price?: boolean
395
+ }
396
+
397
+ // Key-value metadata stored in bundle.bundle_meta
398
+ type BundleMeta = {
399
+ key: string
400
+ value: string
401
+ }
402
+
403
+ // Input for create/update workflow
404
+ type UpdateProductBundleInput = {
405
+ product_id: string
406
+ is_bundle?: boolean
407
+ child_product_ids?: ChildProductConfig[]
408
+ bundle_meta?: BundleMeta[]
409
+ }
410
+
411
+ // Input for delete workflow
412
+ type DeleteProductBundleInput = {
413
+ product_id: string
414
+ }
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Database Schema
420
+
421
+ Migration: `src/modules/bundles/migrations/Migration20241229065751.ts`
422
+
423
+ ```sql
424
+ CREATE TABLE IF NOT EXISTS "bundle" (
425
+ "id" TEXT NOT NULL,
426
+ "child_products" JSONB NOT NULL DEFAULT '{"data":[]}',
427
+ "bundle_meta" JSONB NOT NULL DEFAULT '{"data":[]}',
428
+ "is_bundle" BOOLEAN NOT NULL DEFAULT false,
429
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT now(),
430
+ "updated_at" TIMESTAMPTZ NOT NULL DEFAULT now(),
431
+ "deleted_at" TIMESTAMPTZ DEFAULT NULL,
432
+ CONSTRAINT "bundle_pkey" PRIMARY KEY ("id")
433
+ );
434
+
435
+ CREATE INDEX IF NOT EXISTS "IDX_bundle_deleted_at"
436
+ ON "bundle" ("deleted_at");
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Build & Development
442
+
443
+ **Scripts**:
444
+
445
+ | Command | Description |
446
+ |---|---|
447
+ | `yarn build` | Build plugin for production (`medusa plugin:build`) |
448
+ | `yarn dev` | Start plugin in development mode (`medusa plugin:develop`) |
449
+
450
+ **Output directory**: `.medusa/server/`
451
+
452
+ **Package exports** (from built output):
453
+
454
+ | Export | Path |
455
+ |---|---|
456
+ | `@zaamx/netme-bundle/workflows` | `.medusa/server/src/workflows/index.js` |
457
+ | `@zaamx/netme-bundle/modules/*` | `.medusa/server/src/modules/*/index.js` |
458
+ | `@zaamx/netme-bundle/admin` | `.medusa/server/src/admin/index.mjs` |
459
+ | `@zaamx/netme-bundle/*` | `.medusa/server/src/*.js` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaamx/netme-bundle",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "A starter for Medusa plugins.",
5
5
  "author": "Medusa (https://medusajs.com)",
6
6
  "license": "MIT",