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.
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.
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:
TypeIndexService service type, that enumerates the distinct resource
types present within a storage as visible to the requesting client.
TypeSearchService service type, that returns descriptions of the
resources within a storage matching a filter over types and
indexed relations, as visible to the requesting client.
type.
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 .
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.
The Type Index Service provides a server-managed discovery mechanism to query the distinct resource types available within a storage.
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" }
]
}
The Type Search Service allows clients to retrieve descriptions of the resources within a storage matching a filter over types and indexed relations.
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).
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.
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:
POST body that is not well-formed application/lws+json, or whose
type is not an array, or any of whose elements is neither a string nor an array of strings, MUST be rejected with 400 (Bad Request).
POST request whose entity uses a media type other than application/lws+json MUST be rejected with 415 (Unsupported Media Type).
type value, or any relation target value, that is not a syntactically valid
absolute URI MUST be rejected with 400 (Bad Request). This is distinct from a
well-formed URI that matches no resource, and from a relation the server does not index, both of which yield no results and are not errors (see above).
next link) that the server no longer recognizes or that has expired MUST result in 404 (Not Found) or 410 (Gone); clients SHOULD restart the query from the first page.
400 (Bad Request). A server MUST NOT silently truncate or otherwise narrow such a filter, as doing so could return a superset of the intended results.
Error responses MAY include a representation describing the problem; this specification does not define its format.
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" }
]
}
400 as required by
rather than narrowing them.
Cache-Control: private or
no-store) that prevent reuse across clients.
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.