@molecule/api-resource-inventory 1.0.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/LICENSE +115 -0
- package/dist/authorizers/index.d.ts +133 -0
- package/dist/authorizers/index.d.ts.map +1 -0
- package/dist/authorizers/index.js +188 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +18 -0
- package/dist/handlers/bulkUpdate.d.ts +13 -0
- package/dist/handlers/bulkUpdate.d.ts.map +1 -0
- package/dist/handlers/bulkUpdate.js +168 -0
- package/dist/handlers/confirm.d.ts +13 -0
- package/dist/handlers/confirm.d.ts.map +1 -0
- package/dist/handlers/confirm.js +68 -0
- package/dist/handlers/getAlerts.d.ts +14 -0
- package/dist/handlers/getAlerts.d.ts.map +1 -0
- package/dist/handlers/getAlerts.js +52 -0
- package/dist/handlers/getMovements.d.ts +13 -0
- package/dist/handlers/getMovements.d.ts.map +1 -0
- package/dist/handlers/getMovements.js +62 -0
- package/dist/handlers/getStock.d.ts +13 -0
- package/dist/handlers/getStock.d.ts.map +1 -0
- package/dist/handlers/getStock.js +58 -0
- package/dist/handlers/index.d.ts +9 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +8 -0
- package/dist/handlers/release.d.ts +13 -0
- package/dist/handlers/release.d.ts.map +1 -0
- package/dist/handlers/release.js +67 -0
- package/dist/handlers/reserve.d.ts +13 -0
- package/dist/handlers/reserve.d.ts.map +1 -0
- package/dist/handlers/reserve.js +112 -0
- package/dist/handlers/updateStock.d.ts +13 -0
- package/dist/handlers/updateStock.d.ts.map +1 -0
- package/dist/handlers/updateStock.js +126 -0
- package/dist/i18n.d.ts +14 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +13 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/requestHandlerMap.d.ts +34 -0
- package/dist/requestHandlerMap.d.ts.map +1 -0
- package/dist/requestHandlerMap.js +34 -0
- package/dist/routes.d.ts +61 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +69 -0
- package/dist/types.d.ts +234 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/utilities.d.ts +31 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +79 -0
- package/package.json +54 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for updating stock levels for a product.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { create, findOne, updateById } from '@molecule/api-database';
|
|
7
|
+
import { t } from '@molecule/api-i18n';
|
|
8
|
+
import { logger } from '@molecule/api-logger';
|
|
9
|
+
import { assertInventoryAdmin } from '../authorizers/index.js';
|
|
10
|
+
import { toStockInfo } from '../utilities.js';
|
|
11
|
+
/** Valid adjustment types. */
|
|
12
|
+
const VALID_TYPES = ['add', 'remove', 'set'];
|
|
13
|
+
/**
|
|
14
|
+
* Updates stock for the given product. Creates the stock record if it doesn't exist.
|
|
15
|
+
* @param req - The request with `productId` param and {@link StockAdjustment} body.
|
|
16
|
+
* @param res - The response object.
|
|
17
|
+
*/
|
|
18
|
+
export async function updateStock(req, res) {
|
|
19
|
+
// Admin-only: rewriting stock is a catalog mutation. Fail closed in-handler so
|
|
20
|
+
// the gate holds even if the route middleware is dropped by the injector.
|
|
21
|
+
if (!assertInventoryAdmin(res)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const { productId } = req.params;
|
|
25
|
+
const input = req.body;
|
|
26
|
+
if (input.quantity == null || !VALID_TYPES.includes(input.type)) {
|
|
27
|
+
res.status(400).json({
|
|
28
|
+
error: t('inventory.error.invalidAdjustment', undefined, {
|
|
29
|
+
defaultValue: 'Invalid adjustment: quantity and valid type (add, remove, set) are required',
|
|
30
|
+
}),
|
|
31
|
+
errorKey: 'inventory.error.invalidAdjustment',
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (input.quantity < 0) {
|
|
36
|
+
res.status(400).json({
|
|
37
|
+
error: t('inventory.error.negativeQuantity', undefined, {
|
|
38
|
+
defaultValue: 'Quantity must be non-negative',
|
|
39
|
+
}),
|
|
40
|
+
errorKey: 'inventory.error.negativeQuantity',
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const variantId = input.variantId ?? null;
|
|
46
|
+
const where = [
|
|
47
|
+
{ field: 'productId', operator: '=', value: productId },
|
|
48
|
+
{ field: 'variantId', operator: '=', value: variantId },
|
|
49
|
+
];
|
|
50
|
+
let row = await findOne('inventory_stock', where);
|
|
51
|
+
let newTotal;
|
|
52
|
+
if (!row) {
|
|
53
|
+
// Create new stock record
|
|
54
|
+
newTotal = input.type === 'set' ? input.quantity : input.type === 'add' ? input.quantity : 0;
|
|
55
|
+
const result = await create('inventory_stock', {
|
|
56
|
+
productId,
|
|
57
|
+
variantId,
|
|
58
|
+
total: newTotal,
|
|
59
|
+
reserved: 0,
|
|
60
|
+
lowStockThreshold: 10,
|
|
61
|
+
});
|
|
62
|
+
row = result.data;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Update existing record
|
|
66
|
+
if (input.type === 'add') {
|
|
67
|
+
newTotal = row.total + input.quantity;
|
|
68
|
+
}
|
|
69
|
+
else if (input.type === 'remove') {
|
|
70
|
+
newTotal = row.total - input.quantity;
|
|
71
|
+
if (newTotal < row.reserved) {
|
|
72
|
+
res.status(409).json({
|
|
73
|
+
error: t('inventory.error.insufficientStock', undefined, {
|
|
74
|
+
defaultValue: 'Cannot remove: would drop below reserved quantity',
|
|
75
|
+
}),
|
|
76
|
+
errorKey: 'inventory.error.insufficientStock',
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// 'set'
|
|
83
|
+
newTotal = input.quantity;
|
|
84
|
+
if (newTotal < row.reserved) {
|
|
85
|
+
res.status(409).json({
|
|
86
|
+
error: t('inventory.error.insufficientStock', undefined, {
|
|
87
|
+
defaultValue: 'Cannot set: value is below reserved quantity',
|
|
88
|
+
}),
|
|
89
|
+
errorKey: 'inventory.error.insufficientStock',
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const updateResult = await updateById('inventory_stock', row.id, {
|
|
95
|
+
total: newTotal,
|
|
96
|
+
});
|
|
97
|
+
row = updateResult.data ?? { ...row, total: newTotal };
|
|
98
|
+
}
|
|
99
|
+
// Record movement
|
|
100
|
+
const movementQuantity = input.type === 'add' ? input.quantity : input.type === 'remove' ? -input.quantity : newTotal;
|
|
101
|
+
await create('inventory_movements', {
|
|
102
|
+
productId,
|
|
103
|
+
variantId,
|
|
104
|
+
type: 'adjustment',
|
|
105
|
+
quantity: movementQuantity,
|
|
106
|
+
reason: input.reason ?? null,
|
|
107
|
+
referenceId: null,
|
|
108
|
+
});
|
|
109
|
+
logger.debug('Stock updated', {
|
|
110
|
+
productId,
|
|
111
|
+
variantId,
|
|
112
|
+
type: input.type,
|
|
113
|
+
quantity: input.quantity,
|
|
114
|
+
});
|
|
115
|
+
res.json(toStockInfo(row));
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.error('Failed to update stock', { productId, error });
|
|
119
|
+
res.status(500).json({
|
|
120
|
+
error: t('inventory.error.updateFailed', undefined, {
|
|
121
|
+
defaultValue: 'Failed to update stock',
|
|
122
|
+
}),
|
|
123
|
+
errorKey: 'inventory.error.updateFailed',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale registration for the inventory resource.
|
|
3
|
+
*
|
|
4
|
+
* Errors fall back to inline English defaults via `t(key, vars, { defaultValue })`
|
|
5
|
+
* — this resource ships without a companion locale bond.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether i18n registration has been attempted. Always `true`; this module is
|
|
11
|
+
* a placeholder for symmetry with locale-bonded resources.
|
|
12
|
+
*/
|
|
13
|
+
export declare const i18nRegistered = true;
|
|
14
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,eAAO,MAAM,cAAc,OAAO,CAAA"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale registration for the inventory resource.
|
|
3
|
+
*
|
|
4
|
+
* Errors fall back to inline English defaults via `t(key, vars, { defaultValue })`
|
|
5
|
+
* — this resource ships without a companion locale bond.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether i18n registration has been attempted. Always `true`; this module is
|
|
11
|
+
* a placeholder for symmetry with locale-bonded resources.
|
|
12
|
+
*/
|
|
13
|
+
export const i18nRegistered = true;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory resource for molecule.dev.
|
|
3
|
+
*
|
|
4
|
+
* Provides stock tracking with reservations, low-stock alerts,
|
|
5
|
+
* movement history, and bulk update support.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { routes, requestHandlerMap } from '@molecule/api-resource-inventory'
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* Tables: `src/__setup__/inventory.sql` creates `inventory_stock`,
|
|
15
|
+
* `inventory_reservations`, and `inventory_movements`. An mlcl-scaffolded API
|
|
16
|
+
* replays `__setup__/*.sql` automatically on migrate; anywhere else run it
|
|
17
|
+
* once — nothing at runtime creates them.
|
|
18
|
+
*
|
|
19
|
+
* Stock rows are keyed by `productId` — SHARED app-wide state, not per-user
|
|
20
|
+
* rows. Writing stock (`PUT /inventory/:productId`) and
|
|
21
|
+
* `POST /inventory/bulk` are role-gated and DENY BY DEFAULT (admin session
|
|
22
|
+
* claim or an `@molecule/api-permissions` grant), enforced both as the
|
|
23
|
+
* `requireInventoryAdmin` route middleware and inside the handlers
|
|
24
|
+
* (fail-closed). Out of the box no one can mutate stock — grant the role
|
|
25
|
+
* first; never "fix" the 403 by removing the gate.
|
|
26
|
+
*
|
|
27
|
+
* Reservation flow: `POST /inventory/:productId/reserve` holds quantity →
|
|
28
|
+
* `POST /inventory/reservations/:id/confirm` deducts it,
|
|
29
|
+
* `DELETE /inventory/reservations/:id` releases the hold. All handlers read
|
|
30
|
+
* the authenticated user from `res.locals.session` (mount behind your global
|
|
31
|
+
* auth middleware; 401 otherwise).
|
|
32
|
+
*
|
|
33
|
+
* @e2e
|
|
34
|
+
* Integration checklist — drive the real UI (live preview, no mocks), adapt
|
|
35
|
+
* each item to this app's actual stock/admin screens, and check every box off
|
|
36
|
+
* one by one. Correctness is the whole point here: a box you can't check is a
|
|
37
|
+
* bug to fix, not a skip.
|
|
38
|
+
* - [ ] Setting stock (`PUT /inventory/:productId`, `type:'set'`) then
|
|
39
|
+
* reloading `GET /inventory/:productId` shows the exact `total`/`available`
|
|
40
|
+
* you set — it persisted, not just optimistic UI. `available` = `total` -
|
|
41
|
+
* `reserved`.
|
|
42
|
+
* - [ ] `type:'add' N` raises `total` by exactly N and `type:'remove' N`
|
|
43
|
+
* lowers it by exactly N — verify the arithmetic on the specific
|
|
44
|
+
* `productId`/`variantId`; variant stock is tracked independently, so
|
|
45
|
+
* adjusting one variant must not move another.
|
|
46
|
+
* - [ ] Stock never goes negative: removing or `set`ting below the currently
|
|
47
|
+
* reserved quantity is rejected with a visible 409 error
|
|
48
|
+
* (`inventory.error.insufficientStock`) and the stored `total` is unchanged
|
|
49
|
+
* — never persisted as a negative; reserving more than `available` is
|
|
50
|
+
* likewise rejected (409 `insufficientAvailable`).
|
|
51
|
+
* - [ ] Low-stock crossing flags the item: when `available` falls to or below
|
|
52
|
+
* `lowStockThreshold` (default 10) it reads `isLowStock:true` and appears in
|
|
53
|
+
* `GET /inventory/alerts`; raising stock back above the threshold clears it.
|
|
54
|
+
* - [ ] Every mutation appends an `inventory_movements` row shown in
|
|
55
|
+
* `GET /inventory/:productId/movements` with the signed delta (`+N`/`-N`),
|
|
56
|
+
* type (adjustment/reservation/confirmation), timestamp, and `referenceId`
|
|
57
|
+
* (orderId/reservationId); the acting user is recorded on the reservation.
|
|
58
|
+
* The movement log must reconstruct the current total.
|
|
59
|
+
* - [ ] Concurrency: two reservations or removals fired at once that together
|
|
60
|
+
* exceed `available` don't double-spend the last unit — exactly one
|
|
61
|
+
* succeeds, and final `total`/`reserved` stay consistent (`available` never
|
|
62
|
+
* goes negative).
|
|
63
|
+
* - [ ] AUTHORIZATION — stock mutation is admin-only and denies by default:
|
|
64
|
+
* `PUT /inventory/:productId` and `POST /inventory/bulk` return 403 for a
|
|
65
|
+
* normal signed-in user (no `isAdmin`/`role:'admin'`/`roles`/`permissions`
|
|
66
|
+
* claim) and 401 when signed out; only an admin session can change stock. A
|
|
67
|
+
* customer cannot mutate the shared catalog stock through any endpoint, and
|
|
68
|
+
* one user cannot release/confirm another user's reservation (403
|
|
69
|
+
* `reservationForbidden`).
|
|
70
|
+
*/
|
|
71
|
+
export * from './authorizers/index.js';
|
|
72
|
+
export * from './browser-guard.js';
|
|
73
|
+
export * from './handlers/index.js';
|
|
74
|
+
export * from './i18n.js';
|
|
75
|
+
export * from './requestHandlerMap.js';
|
|
76
|
+
export * from './routes.js';
|
|
77
|
+
export * from './types.js';
|
|
78
|
+
export * from './utilities.js';
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,wBAAwB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory resource for molecule.dev.
|
|
3
|
+
*
|
|
4
|
+
* Provides stock tracking with reservations, low-stock alerts,
|
|
5
|
+
* movement history, and bulk update support.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { routes, requestHandlerMap } from '@molecule/api-resource-inventory'
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* Tables: `src/__setup__/inventory.sql` creates `inventory_stock`,
|
|
15
|
+
* `inventory_reservations`, and `inventory_movements`. An mlcl-scaffolded API
|
|
16
|
+
* replays `__setup__/*.sql` automatically on migrate; anywhere else run it
|
|
17
|
+
* once — nothing at runtime creates them.
|
|
18
|
+
*
|
|
19
|
+
* Stock rows are keyed by `productId` — SHARED app-wide state, not per-user
|
|
20
|
+
* rows. Writing stock (`PUT /inventory/:productId`) and
|
|
21
|
+
* `POST /inventory/bulk` are role-gated and DENY BY DEFAULT (admin session
|
|
22
|
+
* claim or an `@molecule/api-permissions` grant), enforced both as the
|
|
23
|
+
* `requireInventoryAdmin` route middleware and inside the handlers
|
|
24
|
+
* (fail-closed). Out of the box no one can mutate stock — grant the role
|
|
25
|
+
* first; never "fix" the 403 by removing the gate.
|
|
26
|
+
*
|
|
27
|
+
* Reservation flow: `POST /inventory/:productId/reserve` holds quantity →
|
|
28
|
+
* `POST /inventory/reservations/:id/confirm` deducts it,
|
|
29
|
+
* `DELETE /inventory/reservations/:id` releases the hold. All handlers read
|
|
30
|
+
* the authenticated user from `res.locals.session` (mount behind your global
|
|
31
|
+
* auth middleware; 401 otherwise).
|
|
32
|
+
*
|
|
33
|
+
* @e2e
|
|
34
|
+
* Integration checklist — drive the real UI (live preview, no mocks), adapt
|
|
35
|
+
* each item to this app's actual stock/admin screens, and check every box off
|
|
36
|
+
* one by one. Correctness is the whole point here: a box you can't check is a
|
|
37
|
+
* bug to fix, not a skip.
|
|
38
|
+
* - [ ] Setting stock (`PUT /inventory/:productId`, `type:'set'`) then
|
|
39
|
+
* reloading `GET /inventory/:productId` shows the exact `total`/`available`
|
|
40
|
+
* you set — it persisted, not just optimistic UI. `available` = `total` -
|
|
41
|
+
* `reserved`.
|
|
42
|
+
* - [ ] `type:'add' N` raises `total` by exactly N and `type:'remove' N`
|
|
43
|
+
* lowers it by exactly N — verify the arithmetic on the specific
|
|
44
|
+
* `productId`/`variantId`; variant stock is tracked independently, so
|
|
45
|
+
* adjusting one variant must not move another.
|
|
46
|
+
* - [ ] Stock never goes negative: removing or `set`ting below the currently
|
|
47
|
+
* reserved quantity is rejected with a visible 409 error
|
|
48
|
+
* (`inventory.error.insufficientStock`) and the stored `total` is unchanged
|
|
49
|
+
* — never persisted as a negative; reserving more than `available` is
|
|
50
|
+
* likewise rejected (409 `insufficientAvailable`).
|
|
51
|
+
* - [ ] Low-stock crossing flags the item: when `available` falls to or below
|
|
52
|
+
* `lowStockThreshold` (default 10) it reads `isLowStock:true` and appears in
|
|
53
|
+
* `GET /inventory/alerts`; raising stock back above the threshold clears it.
|
|
54
|
+
* - [ ] Every mutation appends an `inventory_movements` row shown in
|
|
55
|
+
* `GET /inventory/:productId/movements` with the signed delta (`+N`/`-N`),
|
|
56
|
+
* type (adjustment/reservation/confirmation), timestamp, and `referenceId`
|
|
57
|
+
* (orderId/reservationId); the acting user is recorded on the reservation.
|
|
58
|
+
* The movement log must reconstruct the current total.
|
|
59
|
+
* - [ ] Concurrency: two reservations or removals fired at once that together
|
|
60
|
+
* exceed `available` don't double-spend the last unit — exactly one
|
|
61
|
+
* succeeds, and final `total`/`reserved` stay consistent (`available` never
|
|
62
|
+
* goes negative).
|
|
63
|
+
* - [ ] AUTHORIZATION — stock mutation is admin-only and denies by default:
|
|
64
|
+
* `PUT /inventory/:productId` and `POST /inventory/bulk` return 403 for a
|
|
65
|
+
* normal signed-in user (no `isAdmin`/`role:'admin'`/`roles`/`permissions`
|
|
66
|
+
* claim) and 401 when signed out; only an admin session can change stock. A
|
|
67
|
+
* customer cannot mutate the shared catalog stock through any endpoint, and
|
|
68
|
+
* one user cannot release/confirm another user's reservation (403
|
|
69
|
+
* `reservationForbidden`).
|
|
70
|
+
*/
|
|
71
|
+
export * from './authorizers/index.js';
|
|
72
|
+
export * from './browser-guard.js';
|
|
73
|
+
export * from './handlers/index.js';
|
|
74
|
+
export * from './i18n.js';
|
|
75
|
+
export * from './requestHandlerMap.js';
|
|
76
|
+
export * from './routes.js';
|
|
77
|
+
export * from './types.js';
|
|
78
|
+
export * from './utilities.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps route handler names to their implementations.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { bulkUpdate } from './handlers/bulkUpdate.js';
|
|
7
|
+
import { confirm } from './handlers/confirm.js';
|
|
8
|
+
import { getAlerts } from './handlers/getAlerts.js';
|
|
9
|
+
import { getMovements } from './handlers/getMovements.js';
|
|
10
|
+
import { getStock } from './handlers/getStock.js';
|
|
11
|
+
import { release } from './handlers/release.js';
|
|
12
|
+
import { reserve } from './handlers/reserve.js';
|
|
13
|
+
import { updateStock } from './handlers/updateStock.js';
|
|
14
|
+
/**
|
|
15
|
+
* Handler map for the inventory resource routes.
|
|
16
|
+
*
|
|
17
|
+
* `requireInventoryAdmin` is the admin authorizer middleware referenced by the
|
|
18
|
+
* `updateStock`/`bulkUpdate` routes. It must live here (as a real handler-map
|
|
19
|
+
* key) so the mlcl injector's route scanner preserves it — a bare middleware
|
|
20
|
+
* string that isn't a handler-map key is silently dropped, which is exactly how
|
|
21
|
+
* the previous bare `'authenticate'` gate became inert.
|
|
22
|
+
*/
|
|
23
|
+
export declare const requestHandlerMap: {
|
|
24
|
+
readonly getStock: typeof getStock;
|
|
25
|
+
readonly updateStock: typeof updateStock;
|
|
26
|
+
readonly reserve: typeof reserve;
|
|
27
|
+
readonly release: typeof release;
|
|
28
|
+
readonly confirm: typeof confirm;
|
|
29
|
+
readonly getAlerts: typeof getAlerts;
|
|
30
|
+
readonly getMovements: typeof getMovements;
|
|
31
|
+
readonly bulkUpdate: typeof bulkUpdate;
|
|
32
|
+
readonly requireInventoryAdmin: import("@molecule/api-resource").MoleculeRequestHandler;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=requestHandlerMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestHandlerMap.d.ts","sourceRoot":"","sources":["../src/requestHandlerMap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;CAUpB,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps route handler names to their implementations.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { requireInventoryAdmin } from './authorizers/index.js';
|
|
7
|
+
import { bulkUpdate } from './handlers/bulkUpdate.js';
|
|
8
|
+
import { confirm } from './handlers/confirm.js';
|
|
9
|
+
import { getAlerts } from './handlers/getAlerts.js';
|
|
10
|
+
import { getMovements } from './handlers/getMovements.js';
|
|
11
|
+
import { getStock } from './handlers/getStock.js';
|
|
12
|
+
import { release } from './handlers/release.js';
|
|
13
|
+
import { reserve } from './handlers/reserve.js';
|
|
14
|
+
import { updateStock } from './handlers/updateStock.js';
|
|
15
|
+
/**
|
|
16
|
+
* Handler map for the inventory resource routes.
|
|
17
|
+
*
|
|
18
|
+
* `requireInventoryAdmin` is the admin authorizer middleware referenced by the
|
|
19
|
+
* `updateStock`/`bulkUpdate` routes. It must live here (as a real handler-map
|
|
20
|
+
* key) so the mlcl injector's route scanner preserves it — a bare middleware
|
|
21
|
+
* string that isn't a handler-map key is silently dropped, which is exactly how
|
|
22
|
+
* the previous bare `'authenticate'` gate became inert.
|
|
23
|
+
*/
|
|
24
|
+
export const requestHandlerMap = {
|
|
25
|
+
getStock,
|
|
26
|
+
updateStock,
|
|
27
|
+
reserve,
|
|
28
|
+
release,
|
|
29
|
+
confirm,
|
|
30
|
+
getAlerts,
|
|
31
|
+
getMovements,
|
|
32
|
+
bulkUpdate,
|
|
33
|
+
requireInventoryAdmin: requireInventoryAdmin(),
|
|
34
|
+
};
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory route definitions.
|
|
3
|
+
*
|
|
4
|
+
* Declarative routing — mlcl inject reads these to generate the Express router.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Inventory resource routes.
|
|
10
|
+
*
|
|
11
|
+
* All routes require authentication. The destructive admin-side mutations
|
|
12
|
+
* (`updateStock`, `bulkUpdate`) are additionally gated by the
|
|
13
|
+
* `requireInventoryAdmin` middleware — a real `requestHandlerMap` key (see
|
|
14
|
+
* {@link requireInventoryAdmin}) so the injector preserves it; the previously
|
|
15
|
+
* declared bare `'authenticate'` string was silently dropped by the route
|
|
16
|
+
* scanner, leaving stock open to rewrite by any authenticated user. Each
|
|
17
|
+
* admin/reservation handler additionally re-checks authorization internally, so
|
|
18
|
+
* the gate holds even if a consumer wires the routes without these middlewares.
|
|
19
|
+
*/
|
|
20
|
+
export declare const routes: readonly [{
|
|
21
|
+
readonly method: "get";
|
|
22
|
+
readonly path: "/inventory/:productId";
|
|
23
|
+
readonly handler: "getStock";
|
|
24
|
+
readonly middlewares: readonly ["authenticate"];
|
|
25
|
+
}, {
|
|
26
|
+
readonly method: "put";
|
|
27
|
+
readonly path: "/inventory/:productId";
|
|
28
|
+
readonly handler: "updateStock";
|
|
29
|
+
readonly middlewares: readonly ["requireInventoryAdmin"];
|
|
30
|
+
}, {
|
|
31
|
+
readonly method: "post";
|
|
32
|
+
readonly path: "/inventory/:productId/reserve";
|
|
33
|
+
readonly handler: "reserve";
|
|
34
|
+
readonly middlewares: readonly ["authenticate"];
|
|
35
|
+
}, {
|
|
36
|
+
readonly method: "delete";
|
|
37
|
+
readonly path: "/inventory/reservations/:reservationId";
|
|
38
|
+
readonly handler: "release";
|
|
39
|
+
readonly middlewares: readonly ["authenticate"];
|
|
40
|
+
}, {
|
|
41
|
+
readonly method: "post";
|
|
42
|
+
readonly path: "/inventory/reservations/:reservationId/confirm";
|
|
43
|
+
readonly handler: "confirm";
|
|
44
|
+
readonly middlewares: readonly ["authenticate"];
|
|
45
|
+
}, {
|
|
46
|
+
readonly method: "get";
|
|
47
|
+
readonly path: "/inventory/alerts";
|
|
48
|
+
readonly handler: "getAlerts";
|
|
49
|
+
readonly middlewares: readonly ["authenticate"];
|
|
50
|
+
}, {
|
|
51
|
+
readonly method: "get";
|
|
52
|
+
readonly path: "/inventory/:productId/movements";
|
|
53
|
+
readonly handler: "getMovements";
|
|
54
|
+
readonly middlewares: readonly ["authenticate"];
|
|
55
|
+
}, {
|
|
56
|
+
readonly method: "post";
|
|
57
|
+
readonly path: "/inventory/bulk";
|
|
58
|
+
readonly handler: "bulkUpdate";
|
|
59
|
+
readonly middlewares: readonly ["requireInventoryAdmin"];
|
|
60
|
+
}];
|
|
61
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDT,CAAA"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory route definitions.
|
|
3
|
+
*
|
|
4
|
+
* Declarative routing — mlcl inject reads these to generate the Express router.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Inventory resource routes.
|
|
10
|
+
*
|
|
11
|
+
* All routes require authentication. The destructive admin-side mutations
|
|
12
|
+
* (`updateStock`, `bulkUpdate`) are additionally gated by the
|
|
13
|
+
* `requireInventoryAdmin` middleware — a real `requestHandlerMap` key (see
|
|
14
|
+
* {@link requireInventoryAdmin}) so the injector preserves it; the previously
|
|
15
|
+
* declared bare `'authenticate'` string was silently dropped by the route
|
|
16
|
+
* scanner, leaving stock open to rewrite by any authenticated user. Each
|
|
17
|
+
* admin/reservation handler additionally re-checks authorization internally, so
|
|
18
|
+
* the gate holds even if a consumer wires the routes without these middlewares.
|
|
19
|
+
*/
|
|
20
|
+
export const routes = [
|
|
21
|
+
{
|
|
22
|
+
method: 'get',
|
|
23
|
+
path: '/inventory/:productId',
|
|
24
|
+
handler: 'getStock',
|
|
25
|
+
middlewares: ['authenticate'],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
method: 'put',
|
|
29
|
+
path: '/inventory/:productId',
|
|
30
|
+
handler: 'updateStock',
|
|
31
|
+
middlewares: ['requireInventoryAdmin'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
method: 'post',
|
|
35
|
+
path: '/inventory/:productId/reserve',
|
|
36
|
+
handler: 'reserve',
|
|
37
|
+
middlewares: ['authenticate'],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
method: 'delete',
|
|
41
|
+
path: '/inventory/reservations/:reservationId',
|
|
42
|
+
handler: 'release',
|
|
43
|
+
middlewares: ['authenticate'],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
method: 'post',
|
|
47
|
+
path: '/inventory/reservations/:reservationId/confirm',
|
|
48
|
+
handler: 'confirm',
|
|
49
|
+
middlewares: ['authenticate'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
method: 'get',
|
|
53
|
+
path: '/inventory/alerts',
|
|
54
|
+
handler: 'getAlerts',
|
|
55
|
+
middlewares: ['authenticate'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
method: 'get',
|
|
59
|
+
path: '/inventory/:productId/movements',
|
|
60
|
+
handler: 'getMovements',
|
|
61
|
+
middlewares: ['authenticate'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
method: 'post',
|
|
65
|
+
path: '/inventory/bulk',
|
|
66
|
+
handler: 'bulkUpdate',
|
|
67
|
+
middlewares: ['requireInventoryAdmin'],
|
|
68
|
+
},
|
|
69
|
+
];
|