1"use strict";(globalThis.webpackChunkrcommon_website=globalThis.webpackChunkrcommon_website||[]).push([[12075],{6893(e,n,t){t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>c,toc:()=>d});var i=t(86070),s=t(81753),a=t(43292);const o={title:"Overview",sidebar_position:1,description:"Multi-tenancy abstraction for RCommon that automatically scopes repository queries and entity stamps to the active tenant via ITenantIdAccessor and IMultiTenant."},r="Multi-Tenancy Overview",c={id:"multi-tenancy/overview",title:"Overview",description:"Multi-tenancy abstraction for RCommon that automatically scopes repository queries and entity stamps to the active tenant via ITenantIdAccessor and IMultiTenant.",source:"@site/versioned_docs/version-3.1.3/multi-tenancy/overview.mdx",sourceDirName:"multi-tenancy",slug:"/multi-tenancy/overview",permalink:"/docs/3.1.3/multi-tenancy/overview",draft:!1,unlisted:!1,editUrl:"https://github.com/RCommon-Team/RCommon/tree/main/website/versioned_docs/version-3.1.3/multi-tenancy/overview.mdx",tags:[],version:"3.1.3",sidebarPosition:1,frontMatter:{title:"Overview",sidebar_position:1,description:"Multi-tenancy abstraction for RCommon that automatically scopes repository queries and entity stamps to the active tenant via ITenantIdAccessor and IMultiTenant."},sidebar:"docsSidebar",previous:{title:"Multi-Tenancy",permalink:"/docs/3.1.3/category/multi-tenancy"},next:{title:"Finbuckle",permalink:"/docs/3.1.3/multi-tenancy/finbuckle"}},l={},d=[{value:"Overview",id:"overview",level:2},{value:"Core abstractions",id:"core-abstractions",level:3},{value:"Entity-level isolation",id:"entity-level-isolation",level:3},{value:"Stamping semantics",id:"stamping-semantics",level:3},{value:"Installation",id:"installation",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Usage",id:"usage",level:2},{value:"Tenant-scoped entities",id:"tenant-scoped-entities",level:3},{value:"Bypassing tenant isolation for one call: <code>TenantScope.Bypass()</code>",id:"bypassing-tenant-isolation-for-one-call-tenantscopebypass",level:3},{value:"Worked scenario: tenant bootstrap",id:"worked-scenario-tenant-bootstrap",level:4},{value:"Opting in a custom <code>ITenantIdAccessor</code>",id:"opting-in-a-custom-itenantidaccessor",level:4},{value:"API Summary",id:"api-summary",level:2}];function h(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"multi-tenancy-overview",children:"Multi-Tenancy Overview"}),"\n",(0,i.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,i.jsx)(n.p,{children:"Multi-tenancy support in RCommon allows a single application instance to serve multiple tenants while keeping their data isolated. The framework provides a thin abstraction layer over third-party tenant resolution libraries so that your domain and persistence code remains unaware of how tenants are identified."}),"\n",(0,i.jsx)(n.p,{children:"The design is split across two concerns:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Tenant resolution"})," \u2014 determining which tenant is active for the current request. This is delegated to a provider (currently Finbuckle) that inspects the HTTP context, claims, route data, or any other signal you configure."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Tenant filtering"})," \u2014 automatically scoping repository queries and entity stamps to the active tenant. This happens inside RCommon's repository implementations whenever the entity implements ",(0,i.jsx)(n.code,{children:"IMultiTenant"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"core-abstractions",children:"Core abstractions"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," is the single point of integration between tenant resolution and the persistence layer. Its ",(0,i.jsx)(n.code,{children:"GetTenantId()"})," method is called by repositories whenever they need to apply tenant isolation. The default registration (",(0,i.jsx)(n.code,{children:"NullTenantIdAccessor"}),") returns ",(0,i.jsx)(n.code,{children:"null"}),", which disables filtering entirely \u2014 useful during bootstrapping or when multi-tenancy is not yet configured."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"IMultiTenantBuilder"})," is the fluent builder interface that all provider-specific builders implement. It gives providers access to the DI service collection so they can replace ",(0,i.jsx)(n.code,{children:"NullTenantIdAccessor"})," with their own implementation."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"WithMultiTenancy<TBuilder>"})," is the extension method on ",(0,i.jsx)(n.code,{children:"IRCommonBuilder"})," that wires a provider into the RCommon startup pipeline. It constructs the builder by convention (via ",(0,i.jsx)(n.code,{children:"Activator.CreateInstance"}),") and passes it to the configuration action you supply."]}),"\n",(0,i.jsx)(n.h3,{id:"entity-level-isolation",children:"Entity-level isolation"}),"\n",(0,i.jsxs)(n.p,{children:["Any entity that implements ",(0,i.jsx)(n.code,{children:"IMultiTenant"})," participates in automatic isolation:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"using RCommon.Entities;\n\npublic class Invoice : BusinessEntity<Guid>, IMultiTenant\n{\n public string Number { get; set; } = string.Empty;\n public decimal Amount { get; set; }\n\n // Populated automatically by the repository on add/update.\n public string? TenantId { get; set; }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["At query time the repository reads the current ",(0,i.jsx)(n.code,{children:"TenantId"})," from ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," and appends a filter expression so only records belonging to that tenant are returned. At write time the same value is stamped onto the entity before it is persisted."]}),"\n",(0,i.jsxs)(n.p,{children:["When ",(0,i.jsx)(n.code,{children:"GetTenantId()"})," returns ",(0,i.jsx)(n.code,{children:"null"})," or an empty string all tenant filtering is bypassed, which is intentional for administrative scenarios where a privileged caller needs cross-tenant access."]}),"\n",(0,i.jsx)(n.h3,{id:"stamping-semantics",children:"Stamping semantics"}),"\n",(0,i.jsxs)(n.p,{children:["The exact guard applied when a repository writes an entity (",(0,i.jsx)(n.code,{children:"MultiTenantHelper.SetTenantIdIfApplicable"}),") is:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["If the resolved tenant ID is ",(0,i.jsx)(n.code,{children:"null"})," or empty, stamping is ",(0,i.jsx)(n.strong,{children:"skipped entirely"})," \u2014 the entity's existing ",(0,i.jsx)(n.code,{children:"TenantId"})," (if any) is left untouched, not overwritten with ",(0,i.jsx)(n.code,{children:"null"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:["If the resolved tenant ID is non-empty, it ",(0,i.jsx)(n.strong,{children:"unconditionally overwrites"})," whatever the entity's ",(0,i.jsx)(n.code,{children:"TenantId"})," already was."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["This asymmetry is what makes ",(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})," (below) safe to use for the tenant-bootstrap scenario: suspending resolution down to ",(0,i.jsx)(n.code,{children:"null"}
1)," for the scope's lifetime means stamping is skipped, so an aggregate's explicitly-set ",(0,i.jsx)(n.code,{children:"TenantId"})," survives ",(0,i.jsx)(n.code,{children:"AddAsync"})," unmodified."]}),"\n",(0,i.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,i.jsx)(a.A,{packageName:"RCommon.MultiTenancy"}),"\n",(0,i.jsx)(n.p,{children:"A provider package is also required. For Finbuckle:"}),"\n",(0,i.jsx)(a.A,{packageName:"RCommon.Finbuckle"}),"\n",(0,i.jsx)(n.h2,{id:"configuration",children:"Configuration"}),"\n",(0,i.jsxs)(n.p,{children:["Call ",(0,i.jsx)(n.code,{children:"WithMultiTenancy<TBuilder>"})," inside your RCommon startup block, passing the concrete builder type for the provider you are using:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'using RCommon;\nusing RCommon.Finbuckle;\nusing Finbuckle.MultiTenant;\n\n// Configure the tenant resolution strategy (Finbuckle\'s own API).\nbuilder.Services.AddMultiTenant<TenantInfo>()\n .WithHeaderStrategy("X-Tenant-Id")\n .WithConfigurationStore();\n\n// Wire Finbuckle into RCommon.\nbuilder.Services.AddRCommon(config =>\n{\n config\n .WithClaimsAndPrincipalAccessorForWeb()\n .WithPersistence<EFCorePersistenceBuilder>(ef =>\n {\n ef.AddDbContext<AppDbContext>(\n "App",\n options => options.UseSqlServer(connectionString));\n })\n .WithMultiTenancy<FinbuckleMultiTenantBuilder<TenantInfo>>(mt =>\n {\n // FinbuckleTenantIdAccessor is registered automatically.\n // No further configuration is required here unless you need\n // to register custom services against the builder.\n });\n});\n'})}),"\n",(0,i.jsx)(n.admonition,{title:"Modular composition",type:"tip",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"WithMultiTenancy<TBuilder>"})," is a cache-aware sub-builder verb. When multiple modules call ",(0,i.jsx)(n.code,{children:"WithMultiTenancy<FinbuckleMultiTenantBuilder<TenantInfo>>(...)"})," with the same builder type, the cached builder is reused and each module's configuration action runs against the same instance. ",(0,i.jsx)(n.code,{children:"FinbuckleTenantIdAccessor"})," is registered exactly once. This lets modules independently add tenant-aware services (custom tenant stores, per-tenant options) without coordinating wiring. See ",(0,i.jsx)(n.a,{href:"/docs/3.1.3/core-concepts/modular-composition",children:"Modular Composition"})," for the full conflict matrix."]})}),"\n",(0,i.jsx)(n.h2,{id:"usage",children:"Usage"}),"\n",(0,i.jsx)(n.h3,{id:"tenant-scoped-entities",children:"Tenant-scoped entities"}),"\n",(0,i.jsxs)(n.p,{children:["Mark entities with ",(0,i.jsx)(n.code,{children:"IMultiTenant"})," to opt into automatic isolation:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public class Product : BusinessEntity<int>, IMultiTenant\n{\n public string Name { get; set; } = string.Empty;\n public decimal Price { get; set; }\n\n // Set automatically; do not assign manually in normal usage.\n public string? TenantId { get; set; }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"Repository reads are filtered to the current tenant transparently:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"// Returns only the current tenant's products.\nICollection<Product> products = await _repository.FindAsync(\n p => p.Price > 0, cancellationToken);\n"})}),"\n",(0,i.jsx)(n.p,{children:"Repository writes stamp the current tenant ID automatically:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'// TenantId is populated from ITenantIdAccessor before INSERT.\nawait _repository.AddAsync(new Product { Name = "Widget", Price = 9.99m }, cancellationToken);\n'})}),"\n",(0,i.jsxs)(n.h3,{id:"bypassing-tenant-isolation-for-one-call-tenantscopebypass",children:["Bypassing tenant isolation for one call: ",(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})]}),"\n",(0,i.jsxs)(n.p,{children:["Swapping in a different ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," registration is an all-or-nothing, DI-configuration-time change \u2014 it affects every request for the life of the process. Most cross-tenant needs are narrower than that: one specific call (list every tenant a super-admin belongs to, create the first row for a brand-new tenant) that should skip ambient scoping without touching anything else."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})," (",(0,i.jsx)(n.code,{children:"RCommon.Security.Claims"}),") is a scoped, per-call primitive for exactly this. While the scope returned by ",(0,i.jsx)(n.code,{children:"Bypass()"})," is active \u2014 including across ",(0,i.jsx)(n.code,{children:"await"})," continuations, via ",(0,i.jsx)(n.code,{children:"AsyncLocal<bool>"})," \u2014 any accessor wrapped with ",(0,i.jsx)(n.code,{children:"TenantScopeAwareTenantIdAccessor"})," resolves to ",(0,i.jsx)(n.code,{children:"null"}),', which every repository already treats as "skip filtering / skip stamping":']}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"using RCommon.Security.Claims;\n\npublic class TenantAdminService\n{\n private readonly IGraphRepository<Tenant> _tenants;\n\n public async Task<ICollection<Tenant>> ListAllTenantsAsync(CancellationToken ct)\n {\n // Requires the caller to already be authorized for cross-tenant access --\n // TenantScope.Bypass() has no authorization check of its own.\n using (TenantScope.Bypass())\n {\n return await _tenants.FindAsync(t => true, ct);\n }\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Always dispose the returned handle \u2014 a ",(0,i.jsx)(n.code,{children:"using"})," block is strongly recommended. If it is never disposed (e.g. an exception path that skips the ",(0,i.jsx)(n.code,{children:"using"}),' block due to a bug in calling code), the bypass remains active for the rest of the current logical call context. Nested scopes compose correctly: disposing an inner scope while an outer scope is still active restores "bypassed," not "not bypassed."']}),"\n",(0,i.jsxs)(n.p,{children:["RCommon wraps its own built-in accessors with ",(0,i.jsx)(n.code,{children:"TenantScopeAwareTenantIdAccessor"})," automatically at their existing registration sites \u2014 ",(0,i.jsx)(n.code,{children:"ClaimsTenantIdAccessor"})," (via ",(0,i.jsx)(n.code,{children:"WithClaimsAndPrincipalAccessor()"}),") and ",(0,i.jsx)(n.code,{children:"FinbuckleTenantIdAccessor<TTenantInfo>"})," (via ",(0,i.jsx)(n.code,{children:"FinbuckleMultiTenantBuilder<TTenantInfo>"}),") both honor ",(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})," with no code change required. ",(0,i.jsx)(n.code,{children:"NullTenantIdAccessor"}
1)," is intentionally not wrapped, since it always returns ",(0,i.jsx)(n.code,{children:"null"})," already."]}),"\n",(0,i.jsx)(n.h4,{id:"worked-scenario-tenant-bootstrap",children:"Worked scenario: tenant bootstrap"}),"\n",(0,i.jsxs)(n.p,{children:["Creating the very first row for a brand-new tenant is the one case where you ",(0,i.jsx)(n.em,{children:"want"})," an explicitly-set ",(0,i.jsx)(n.code,{children:"TenantId"})," to survive ",(0,i.jsx)(n.code,{children:"AddAsync"})," untouched \u2014 not stamped with ",(0,i.jsx)(n.code,{children:"null"}),", and not stamped with whatever tenant the caller happens to be ambiently scoped to:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public class CreateTenantCommandHandler\n{\n private readonly IAggregateRepository<Tenant, Guid> _tenants;\n\n public async Task HandleAsync(CreateTenantCommand cmd, CancellationToken ct)\n {\n using (TenantScope.Bypass())\n {\n var tenant = new Tenant(cmd.NewTenantId, cmd.Name);\n // tenant.TenantId is set explicitly by the aggregate's own constructor. With the\n // bypass active, ITenantIdAccessor.GetTenantId() resolves to null, so the stamping\n // guard above skips stamping entirely -- the explicitly-set TenantId is left as-is.\n await _tenants.AddAsync(tenant, ct);\n }\n }\n}\n"})}),"\n",(0,i.jsxs)(n.h4,{id:"opting-in-a-custom-itenantidaccessor",children:["Opting in a custom ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})]}),"\n",(0,i.jsxs)(n.p,{children:["If you've written your own ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," implementation instead of using ",(0,i.jsx)(n.code,{children:"ClaimsTenantIdAccessor"})," or Finbuckle's accessor, wrap it with ",(0,i.jsx)(n.code,{children:"TenantScopeAwareTenantIdAccessor"})," at your own registration site to get the same bypass support \u2014 RCommon has no way to intercept a registration it doesn't own:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"services.AddTransient<MyCustomTenantIdAccessor>();\nservices.AddTransient<ITenantIdAccessor>(sp =>\n new TenantScopeAwareTenantIdAccessor(sp.GetRequiredService<MyCustomTenantIdAccessor>()));\n"})}),"\n",(0,i.jsx)(n.admonition,{title:"Authorization is the caller's responsibility",type:"danger",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})," removes tenant isolation for its scope with no authorization check of its own \u2014 the same trust model as ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," itself. Gate any code path that calls ",(0,i.jsx)(n.code,{children:"Bypass()"})," behind your own authorization check (e.g. an admin role). RCommon has no way to know which callers should be allowed to see cross-tenant data."]})}),"\n",(0,i.jsx)(n.h2,{id:"api-summary",children:"API Summary"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Type"}),(0,i.jsx)(n.th,{children:"Package"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Security"})}),(0,i.jsxs)(n.td,{children:["Returns the current tenant ID; ",(0,i.jsx)(n.code,{children:"null"})," disables all filtering"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"NullTenantIdAccessor"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Security"})}),(0,i.jsxs)(n.td,{children:["Default implementation; always returns ",(0,i.jsx)(n.code,{children:"null"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"ClaimsTenantIdAccessor"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Security"})}),(0,i.jsx)(n.td,{children:"Reads tenant ID from the authenticated user's claims"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"TenantScope"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Security"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"Bypass()"})," suspends tenant scoping for a scoped, ambient, per-call lifetime"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"TenantScopeAwareTenantIdAccessor"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Security"})}),(0,i.jsxs)(n.td,{children:["Decorator making any ",(0,i.jsx)(n.code,{children:"ITenantIdAccessor"})," honor ",(0,i.jsx)(n.code,{children:"TenantScope.Bypass()"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"IMultiTenantBuilder"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.MultiTenancy"})}),(0,i.jsx)(n.td,{children:"Builder interface that provider packages implement"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"MultiTenancyBuilderExtensions"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.MultiTenancy"})}),(0,i.jsxs)(n.td,{children:["Provides ",(0,i.jsx)(n.code,{children:"WithMultiTenancy<TBuilder>()"})," on ",(0,i.jsx)(n.code,{children:"IRCommonBuilder"})]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"IMultiTenant"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"RCommon.Entities"})}),(0,i.jsxs)(n.td,{children:["Entity marker interface; exposes ",(0,i.jsx)(n.code,{children:"string? TenantId"})]})]})]})]})]})}function u(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},43292(e,n,t){t.d(n,{A:()=>d});var i=t(30758);const s="container_xjrG",a="label_Y4p8",o="commandRow_FY5I",r="command_m7Qs",c="copyButton_u1GK";var l=t(86070);function d({packageName:e,version:n}){const[t,d]=(0,i.useState)(!1),h=n?`dotnet add package ${e} --version ${n}`:`dotnet add package ${e}`;return(0,l.jsxs)("div",{className:s,children:[(0,l.jsx)("div",{className:a,children:"NuGet Package"}),(0,l.jsxs)("div",{className:o,children:[(0,l.jsx)("code",{className:r,children:h}),(0,l.jsx)("button",{className:c,onClick:()=>{navigator.clipboard.writeText(h),d(!0),setTimeout(()=>d(!1),2e3)},title:"Copy to clipboard",children:t?"\u2713":"\ud83d\udccb"})]})]})}},81753(e,n,t){t.d(n,{R:()=>o,x:()=>r});var i=t(30758);const s={},a=i.createContext(s);function o(e){const n=i.useContext(a);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.