This document extends the Linked Web Storage (LWS) protocol with server-managed services for type-based discovery: a Type Index Service that enumerates the distinct resource types present within a storage, and a Type Search Service that returns the resources matching a type filter. The specification defines how servers derive types, the query model and its error handling, and the authorization requirements that keep responses client-specific. Both services are discoverable via the storage description resource.

This is an unofficial proposal.

Introduction

The [[!LWS-PROTOCOL|Linked Web Storage]] protocol defines a resource-centric storage system built on HTTP. While the core protocol supports creating, reading, updating, and deleting resources, and containers enumerate the resources they contain, it does not define a mechanism for a client to learn which kinds of resources exist within a storage, or to locate the resources of a given kind, other than by traversing the containment hierarchy one resource at a time.

This specification extends LWS with two server-managed services, discoverable via the storage description resource. The TypeIndexService provides a server-managed discovery mechanism to query the distinct resource types available within a storage, while the TypeSearchService allows clients to retrieve the specific resource URIs matching those types.

Terminology

The terms "storage", "storage description resource", "container", "data resource", "resource manager", and "requesting agent" are defined by [[!LWS-PROTOCOL]].

This specification defines the following additional terms:

Discovery

A storage MAY support the Type Index Service, the Type Search Service, or both. A storage that supports one of these services MUST advertise it with a service object in the service array of its storage description resource: the entry for the Type Index Service MUST have a type equal to TypeIndexService, and the entry for the Type Search Service MUST have a type equal to TypeSearchService.

Each service object MUST include a serviceEndpoint property whose value is the URI of the corresponding service endpoint. Additional properties MAY be present on a service object.

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "id": "https://example.org/storage/alice/",
  "type": "Storage",
  "service": [
    {
      "type": "TypeIndexService",
      "serviceEndpoint": "https://example.org/types/index"
    },
    {
      "type": "TypeSearchService",
      "serviceEndpoint": "https://example.org/types/search"
    }
  ]
}
      

The storage description advertises only the service endpoints. In particular, the set of additional indexed relations a server supports is deliberately not enumerated there or anywhere else; see .

Type and Relation Derivation

To prevent inadvertent or malicious corruption by clients, these services are strictly populated and managed by the server. To mitigate security and performance risks associated with deep-parsing arbitrary resource bodies, servers are not required to parse resource content to discover types. Instead, servers SHOULD derive resource types from HTTP Link headers provided by the client during resource creation or modification, combined with the server's intrinsic knowledge of the resource's state (such as native LWS classes like https://www.w3.org/ns/lws#Container).

Servers MAY additionally derive types from the resource representation itself when they are able to parse it. Implementations are encouraged to do so where feasible, as richer type discovery improves the utility of the Type Index and Type Search services. When a server enriches the type index from resource content, the types it surfaces MUST be treated identically to those derived from Link headers for the purposes of indexing, search, and authorization filtering.

Type and indexed-relation membership in these services MAY be eventually consistent. Following a write that changes a resource's derivable types or relation targets (creation, update, or deletion), a server SHOULD reflect the change within a bounded, implementation-defined interval; until it does, a response MAY omit a newly matching resource or briefly retain one that no longer matches. Clients MUST NOT assume read-your-writes consistency and SHOULD tolerate transient staleness. This allowance applies only to derivation and MUST NOT be applied to authorization filtering (see ).

Because servers are not required to parse the resource body, the Type Search Service relies heavily on the types explicitly declared via HTTP headers or managed by the server. If a client includes a type exclusively within a resource's internal graph but fails to provide the corresponding Link header, that type may not be discoverable via these services.

Type Index Service

The Type Index Service provides a server-managed discovery mechanism to query the distinct resource types available within a storage.

GET [TypeIndexService]

Returns a paginated TypeIndex as application/lws+json, following the standard LWS pagination model defined by [[!LWS-PROTOCOL]]: page URIs are conveyed in Link headers—not in the response body—and are opaque to the client. The body lists the unique types of resources that currently exist within the storage and that the authenticated user is authorized to see. This endpoint accepts no query parameters.

HTTP/1.1 200 OK
Content-Type: application/lws+json
Link: <https://example.org/types/index?page=1>; rel="first"
Link: <https://example.org/types/index?page=2>; rel="next"
Link: <https://example.org/types/index?page=4>; rel="last"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "TypeIndex",
  "totalItems": 142,
  "items": [
    { "id": "https://schema.org/Person" },
    { "id": "https://schema.org/Event" },
    { "id": "https://schema.org/Message" }
  ]
}
        

Type Search Service

The Type Search Service allows clients to retrieve descriptions of the resources within a storage matching a filter over types and indexed relations.

GET [TypeSearchService]

The type query parameter is OPTIONAL and selects resources by their rdf:type (as declared via rel="type" Link headers, server-intrinsic classes, or content; see ).

A single type parameter MAY carry a comma-separated list of URIs, combined with logical OR — a resource matches the group if it declares any of the listed types. Repeating the type parameter combines the groups with logical AND. The filter is therefore a conjunction of disjunctions: ?type=A,B&type=C selects resources matching (A OR B) AND C. A request with no type parameter matches all resources visible to the client.

This conjunctive-normal-form filter is the complete query expressiveness of the service. Servers MAY support nesting, negation, comparison, ordering, or text matching. A type URI that matches no resource yields no results and MUST NOT be treated as an error; empty or duplicate groups MUST be ignored.

The type parameter is the mandatory baseline and MUST be supported by every server that implements the TypeSearchService. A server MAY additionally index other descriptive link relations. Such a relation is filtered by using the relation itself as the query-parameter name — a registered relation name per [[!RFC8288]] or, for an extension relation, its URI — with exactly the same conjunctive-normal-form semantics as type (comma = OR group, repeated parameter = AND group, target values matched against the link targets the resource declares for that relation). Groups belonging to different relations are combined with logical AND. Relation targets are derived by the server in the same way as types (HTTP Link headers, server-intrinsic state, or parsed content) and all sources MUST be treated identically.

The set of additional indexed relations is deliberately NOT enumerated by the server — not in the storage description nor elsewhere — so that it cannot serve as a discovery oracle. A relation the server does not index, like a target value that matches nothing, simply yields no results for that constraint; it MUST NOT be treated as an error, and the two cases MUST be indistinguishable to the client (consistent with the type-matching rule above). Servers MUST NOT index structural or protocol relations, only descriptive relations are eligible.

Clients MUST URL-encode query parameter keys and values.

Returns application/lws+json: A paginated ContainerPage whose items describe the matching LWS resources. Aside from type and any additional relation parameters described above, no other parameters are defined.

A ContainerPage returned by the TypeSearchService is a synthetic result set, not a representation of a container resource. The ContainerPage media type and pagination model are reused only for client convenience; the response does not identify a container, has no containment relationship to its members, is not retrievable or mutable as a resource, and its membership reflects the query rather than any stored hierarchy. Each item is a resource description carrying at least id and type, mirroring a container member; a server MAY include additional descriptive metadata (such as mediaType, size, or modified) but is not required to. The same applies to the POST form.

A single-type query (one URI, no comma) matches a resource that declares that type, even if the resource also declares other types.

A type filter always denotes the type the matched resource itself bears; it never denotes the types of a container's members. The native LWS classes https://www.w3.org/ns/lws#DataResource and https://www.w3.org/ns/lws#Container are ordinary type values: filtering on them selects resources that are themselves data resources or containers, respectively (for example ?type=https://www.w3.org/ns/lws%23Container matches container resources, and ?type=https://schema.org/Person&type=https://www.w3.org/ns/lws%23DataResource matches data resources that are also schema:Person).

POST [TypeSearchService]

MUST support application/lws+json body:

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": [
    ["https://schema.org/Person", "http://xmlns.com/foaf/0.1/Person"],
    "https://www.w3.org/ns/lws#DataResource"
  ]
}
        

type: an array whose elements are combined with AND. Each element is either a single type URI or an array of type URIs combined with OR. The example above selects resources matching (schema:Person OR foaf:Person) AND lws:DataResource. This is the same conjunctive-normal-form filter as the GET form, with the same expressiveness ceiling: servers MUST NOT be required to support nesting, negation, comparison, ordering, or text matching.

The body MAY also include, alongside type, a key for any other descriptive relation (a registered relation name per [[!RFC8288]] or an extension relation URI). Each such key takes the same value form as type and the keys are combined with logical AND, exactly as in the GET form. A relation the server does not index yields no results for that constraint and is indistinguishable from a target value that matches nothing; it MUST NOT be treated as an error.

Returns a paginated ContainerPage whose items describe the matching LWS resources. Aside from type and additional relation keys, no other parameters are defined.

Request Equivalence and Errors

A server implementing the TypeSearchService MUST support both the GET and POST forms. The two forms are equivalent: any filter expressible in one MUST be expressible in the other, and equivalent GET and POST requests MUST return the same result set. A server MUST NOT impose different expressiveness limits on the two forms, except that GET requests are additionally bounded by practical request-URI length limits.

Servers indicate request outcomes using standard HTTP status codes. In particular:

Error responses MAY include a representation describing the problem; this specification does not define its format.

Security and Authorization

Servers MUST enforce authorization. Responses to GET/POST on TypeIndexService or TypeSearchService include only types and resource URIs that the authenticated client is explicitly authorized to read.

Unauthorized entries MUST be omitted entirely. A client must not be able to discover the existence of a specific resource instance, or that a specific type exists in the storage at all, if they do not have the required authorization. This implicit filtering yields a dynamically generated, client-specific type index and search result.

Any count a response exposes, including totalItems, MUST be computed over this client-specific, authorization-filtered view: it is the number of entries the requesting client is authorized to see, never a count of the types or resources present in the storage as a whole. A count that included unauthorized entries would itself reveal their existence and is therefore prohibited.

Authorization filtering MUST be evaluated against the requesting client's current access at the time the request is served, not against any cached or precomputed view. Once an authorization (such as an access grant) is revoked or narrowed, the affected types and resource URIs MUST NOT appear in any subsequent response; the eventual-consistency allowance for type and relation derivation MUST NOT be extended to authorization. A server MAY maintain a derived index for performance, but it MUST apply current authorization as a filter over that index on every request.

Examples

Request
GET /types/index

Response
HTTP/1.1 200 OK
Content-Type: application/lws+json
Link: <https://example.org/types/index?page=1>; rel="first"
Link: <https://example.org/types/index?page=2>; rel="next"
Link: <https://example.org/types/index?page=4>; rel="last"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "TypeIndex",
  "totalItems": 142,
  "items": [
    { "id": "https://schema.org/Person" },
    { "id": "https://schema.org/Event" }
  ]
}
      
Request
GET /types/search?type=https://schema.org/Person&type=https://www.w3.org/ns/lws%23DataResource

Response
HTTP/1.1 200 OK
Content-Type: application/lws+json
Link: <https://example.org/types/search?type=https://schema.org/Person&type=https://www.w3.org/ns/lws%23DataResource&page=1>; rel="first"
Link: <https://example.org/types/search?type=https://schema.org/Person&type=https://www.w3.org/ns/lws%23DataResource&page=2>; rel="next"

{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 27,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4873" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-5882" }
  ]
}
      
Request
GET /types/search?type=https://www.w3.org/ns/lws%23Container

Matches resources whose type includes lws:Container. This does NOT mean
"containers that contain something" - the service has no containment query.
The returned URIs are container resources because each is itself a lws:Container.

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": "Container", "id": "https://example.org/data/people/" },
    { "type": "Container", "id": "https://example.org/data/contacts/" }
  ]
}
      
Request
POST /types/search
Request Body
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": [
    "https://schema.org/Person",
    "https://www.w3.org/ns/lws#DataResource"
  ]
}

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 3,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-9341" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-1123" }
  ]
}
      
Request (GET form: comma = OR, repeated parameter = AND)
GET /types/search?type=https://schema.org/Person,http://xmlns.com/foaf/0.1/Person&type=https://www.w3.org/ns/lws%23DataResource

Equivalent Request (POST form: nested array = OR group, outer array = AND)
POST /types/search
Request Body
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": [
    ["https://schema.org/Person", "http://xmlns.com/foaf/0.1/Person"],
    "https://www.w3.org/ns/lws#DataResource"
  ]
}

Both select resources matching (schema:Person OR foaf:Person) AND lws:DataResource.

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "http://xmlns.com/foaf/0.1/Person"], "id": "https://example.org/data/contact-2208" }
  ]
}
      
Request (GET: type AND a 'describedby' relation; relation name used as the parameter)
GET /types/search?type=https://schema.org/Person&describedby=https://shapes.example/PersonShape

Equivalent Request (POST)
POST /types/search
Request Body
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": ["https://schema.org/Person"],
  "describedby": ["https://shapes.example/PersonShape"]
}

Selects resources that are schema:Person AND declare a 'describedby' link to the given shape.
If the server does not index 'describedby', the constraint yields no results (not an error,
and indistinguishable from no resource declaring that link).

Response
{
  "@context": "https://www.w3.org/ns/lws/v1",
  "type": "ContainerPage",
  "totalItems": 2,
  "items": [
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-4872" },
    { "type": ["DataResource", "https://schema.org/Person"], "id": "https://example.org/data/person-5012" }
  ]
}
      

Security Considerations

Privacy Considerations

Vocabulary

This document defines the following terms in the https://www.w3.org/ns/lws# namespace:

Term URI Description
TypeIndexService lws:TypeIndexService Service type for type index discovery
TypeSearchService lws:TypeSearchService Service type for type search discovery
TypeIndex lws:TypeIndex Document type returned by the Type Index Service

This document also uses the terms Storage, ContainerPage, service, serviceEndpoint, totalItems, and items defined by [[!LWS-PROTOCOL]] and the LWS JSON-LD context.