@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.
Files changed (53) hide show
  1. package/LICENSE +115 -0
  2. package/dist/authorizers/index.d.ts +133 -0
  3. package/dist/authorizers/index.d.ts.map +1 -0
  4. package/dist/authorizers/index.js +188 -0
  5. package/dist/browser-guard.d.ts +2 -0
  6. package/dist/browser-guard.d.ts.map +1 -0
  7. package/dist/browser-guard.js +18 -0
  8. package/dist/handlers/bulkUpdate.d.ts +13 -0
  9. package/dist/handlers/bulkUpdate.d.ts.map +1 -0
  10. package/dist/handlers/bulkUpdate.js +168 -0
  11. package/dist/handlers/confirm.d.ts +13 -0
  12. package/dist/handlers/confirm.d.ts.map +1 -0
  13. package/dist/handlers/confirm.js +68 -0
  14. package/dist/handlers/getAlerts.d.ts +14 -0
  15. package/dist/handlers/getAlerts.d.ts.map +1 -0
  16. package/dist/handlers/getAlerts.js +52 -0
  17. package/dist/handlers/getMovements.d.ts +13 -0
  18. package/dist/handlers/getMovements.d.ts.map +1 -0
  19. package/dist/handlers/getMovements.js +62 -0
  20. package/dist/handlers/getStock.d.ts +13 -0
  21. package/dist/handlers/getStock.d.ts.map +1 -0
  22. package/dist/handlers/getStock.js +58 -0
  23. package/dist/handlers/index.d.ts +9 -0
  24. package/dist/handlers/index.d.ts.map +1 -0
  25. package/dist/handlers/index.js +8 -0
  26. package/dist/handlers/release.d.ts +13 -0
  27. package/dist/handlers/release.d.ts.map +1 -0
  28. package/dist/handlers/release.js +67 -0
  29. package/dist/handlers/reserve.d.ts +13 -0
  30. package/dist/handlers/reserve.d.ts.map +1 -0
  31. package/dist/handlers/reserve.js +112 -0
  32. package/dist/handlers/updateStock.d.ts +13 -0
  33. package/dist/handlers/updateStock.d.ts.map +1 -0
  34. package/dist/handlers/updateStock.js +126 -0
  35. package/dist/i18n.d.ts +14 -0
  36. package/dist/i18n.d.ts.map +1 -0
  37. package/dist/i18n.js +13 -0
  38. package/dist/index.d.ts +79 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +78 -0
  41. package/dist/requestHandlerMap.d.ts +34 -0
  42. package/dist/requestHandlerMap.d.ts.map +1 -0
  43. package/dist/requestHandlerMap.js +34 -0
  44. package/dist/routes.d.ts +61 -0
  45. package/dist/routes.d.ts.map +1 -0
  46. package/dist/routes.js +69 -0
  47. package/dist/types.d.ts +234 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +6 -0
  50. package/dist/utilities.d.ts +31 -0
  51. package/dist/utilities.d.ts.map +1 -0
  52. package/dist/utilities.js +79 -0
  53. 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;
@@ -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
+ };
@@ -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
+ ];