@open-mercato/shared 0.6.6 → 0.6.7-develop.6553.1.2ec7246e04
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/AGENTS.md +18 -0
- package/dist/lib/crud/factory.js +33 -0
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/location/countries.js +10 -4
- package/dist/lib/location/countries.js.map +2 -2
- package/dist/lib/network.js +20 -1
- package/dist/lib/network.js.map +2 -2
- package/dist/lib/string.js +5 -0
- package/dist/lib/string.js.map +2 -2
- package/dist/lib/url-safety.js +14 -7
- package/dist/lib/url-safety.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/payment_gateways/types.js.map +2 -2
- package/package.json +4 -3
- package/src/lib/__tests__/string.test.ts +49 -0
- package/src/lib/__tests__/url-safety.test.ts +28 -0
- package/src/lib/crud/factory.ts +55 -0
- package/src/lib/location/__tests__/countries.test.ts +82 -0
- package/src/lib/location/countries.ts +15 -4
- package/src/lib/network.ts +28 -1
- package/src/lib/string.ts +8 -0
- package/src/lib/url-safety.ts +33 -14
- package/src/modules/payment_gateways/types.ts +2 -0
package/AGENTS.md
CHANGED
|
@@ -52,6 +52,7 @@ yarn workspace @open-mercato/shared build
|
|
|
52
52
|
| `number.ts` | When parsing numeric strings from env/query params with a fallback and optional min/integer constraint | `@open-mercato/shared/lib/number` |
|
|
53
53
|
| `openapi/` | When generating CRUD OpenAPI specs | `@open-mercato/shared/lib/openapi/crud` |
|
|
54
54
|
| `profiler/` | When profiling with `OM_PROFILE` env flag | `@open-mercato/shared/lib/profiler` |
|
|
55
|
+
| `string.ts` | When parsing comma-separated lists from CLI args/query params, or coercing a string to `undefined` when blank | `@open-mercato/shared/lib/string` |
|
|
55
56
|
| `testing/` | When bootstrapping tests — register only what the test needs | `@open-mercato/shared/lib/testing/bootstrap` |
|
|
56
57
|
|
|
57
58
|
## Module Types (`src/modules/`)
|
|
@@ -94,6 +95,23 @@ const results = await findWithDecryption(em, 'Entity', filter, { tenantId, organ
|
|
|
94
95
|
import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
### Comma-Separated List Parsing — MUST use instead of ad-hoc splitting
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`parseCommaSeparatedList(value)` splits on commas, trims each entry, and drops blanks. Non-string
|
|
105
|
+
inputs (`null`/`undefined`) yield `[]`. MUST use it instead of hand-rolling
|
|
106
|
+
`value.split(',').map((s) => s.trim()).filter(Boolean)` when reading CLI flags or query params.
|
|
107
|
+
|
|
108
|
+
Keep the surrounding guard when a call site distinguishes "not supplied" (`undefined`) from
|
|
109
|
+
"supplied but empty" (`[]`) — the helper always returns an array:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const roleNames = rolesCsv ? parseCommaSeparatedList(rolesCsv) : undefined
|
|
113
|
+
```
|
|
114
|
+
|
|
97
115
|
### Browser Storage — use the shared helpers instead of raw `localStorage`
|
|
98
116
|
|
|
99
117
|
Persisted client UI state MUST go through the shared `browser/` helpers, never raw `window.localStorage` reads/writes scattered per component:
|
package/dist/lib/crud/factory.js
CHANGED
|
@@ -264,6 +264,8 @@ function json(data, init) {
|
|
|
264
264
|
headers: { "content-type": "application/json", ...init?.headers || {} }
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
|
+
const SELECTED_ORG_COOKIE = "om_selected_org";
|
|
268
|
+
const CLEAR_SELECTED_ORG_COOKIE = `${SELECTED_ORG_COOKIE}=; Path=/; Max-Age=0; SameSite=Lax`;
|
|
267
269
|
function attachOperationHeader(res, logEntry) {
|
|
268
270
|
if (!res || !(res instanceof Response)) return res;
|
|
269
271
|
if (!logEntry || typeof logEntry !== "object") return res;
|
|
@@ -892,6 +894,26 @@ function makeCrudRoute(opts) {
|
|
|
892
894
|
}
|
|
893
895
|
return { container, auth: scopedAuth, organizationScope: scope, selectedOrganizationId, organizationIds, request };
|
|
894
896
|
}
|
|
897
|
+
function rejectInvalidOrgSelection(ctx, action) {
|
|
898
|
+
if (!ormCfg.orgField) return null;
|
|
899
|
+
if (!ctx.organizationScope?.selectionRejected) return null;
|
|
900
|
+
logForbidden({
|
|
901
|
+
resourceKind,
|
|
902
|
+
action,
|
|
903
|
+
reason: "organization_selection_invalid",
|
|
904
|
+
userId: ctx.auth?.sub ?? null,
|
|
905
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
906
|
+
organizationIds: ctx.organizationIds
|
|
907
|
+
});
|
|
908
|
+
const headers = action === "list" ? { "set-cookie": CLEAR_SELECTED_ORG_COOKIE } : {};
|
|
909
|
+
return json(
|
|
910
|
+
{
|
|
911
|
+
error: "Your selected organization is no longer available. Please re-select an organization and try again.",
|
|
912
|
+
code: "organization_selection_invalid"
|
|
913
|
+
},
|
|
914
|
+
{ status: 422, headers }
|
|
915
|
+
);
|
|
916
|
+
}
|
|
895
917
|
async function GET(request) {
|
|
896
918
|
const profiler = createCrudProfiler(resourceKind, "list");
|
|
897
919
|
const requestMeta = { method: request.method };
|
|
@@ -919,6 +941,11 @@ function makeCrudRoute(opts) {
|
|
|
919
941
|
finishProfile({ reason: "unauthorized" });
|
|
920
942
|
return json({ error: "Unauthorized" }, { status: 401 });
|
|
921
943
|
}
|
|
944
|
+
const listSelectionRejected = rejectInvalidOrgSelection(ctx, "list");
|
|
945
|
+
if (listSelectionRejected) {
|
|
946
|
+
finishProfile({ reason: "organization_selection_invalid" });
|
|
947
|
+
return listSelectionRejected;
|
|
948
|
+
}
|
|
922
949
|
if (!opts.list) {
|
|
923
950
|
finishProfile({ reason: "list_not_configured" });
|
|
924
951
|
return json({ error: "Not implemented" }, { status: 501 });
|
|
@@ -1510,6 +1537,8 @@ function makeCrudRoute(opts) {
|
|
|
1510
1537
|
if (!opts.create && !useCommand) return json({ error: "Not implemented" }, { status: 501 });
|
|
1511
1538
|
const ctx = await withCtx(request);
|
|
1512
1539
|
if (!ctx.auth) return json({ error: "Unauthorized" }, { status: 401 });
|
|
1540
|
+
const createSelectionRejected = rejectInvalidOrgSelection(ctx, "create");
|
|
1541
|
+
if (createSelectionRejected) return createSelectionRejected;
|
|
1513
1542
|
if (ormCfg.orgField && ctx.organizationIds && ctx.organizationIds.length === 0) {
|
|
1514
1543
|
logForbidden({
|
|
1515
1544
|
resourceKind,
|
|
@@ -1753,6 +1782,8 @@ function makeCrudRoute(opts) {
|
|
|
1753
1782
|
if (!opts.update && !useCommand) return json({ error: "Not implemented" }, { status: 501 });
|
|
1754
1783
|
const ctx = await withCtx(request);
|
|
1755
1784
|
if (!ctx.auth) return json({ error: "Unauthorized" }, { status: 401 });
|
|
1785
|
+
const updateSelectionRejected = rejectInvalidOrgSelection(ctx, "update");
|
|
1786
|
+
if (updateSelectionRejected) return updateSelectionRejected;
|
|
1756
1787
|
if (ormCfg.orgField && ctx.organizationIds && ctx.organizationIds.length === 0) {
|
|
1757
1788
|
logForbidden({
|
|
1758
1789
|
resourceKind,
|
|
@@ -2053,6 +2084,8 @@ function makeCrudRoute(opts) {
|
|
|
2053
2084
|
try {
|
|
2054
2085
|
const ctx = await withCtx(request);
|
|
2055
2086
|
if (!ctx.auth) return json({ error: "Unauthorized" }, { status: 401 });
|
|
2087
|
+
const deleteSelectionRejected = rejectInvalidOrgSelection(ctx, "delete");
|
|
2088
|
+
if (deleteSelectionRejected) return deleteSelectionRejected;
|
|
2056
2089
|
if (ormCfg.orgField && ctx.organizationIds && ctx.organizationIds.length === 0) {
|
|
2057
2090
|
logForbidden({
|
|
2058
2091
|
resourceKind,
|