Solid State KV
An example of how to authenticate an application with Solid, and persist data to a Solid Pod.
Explore how it works on the KV Playground
Table of Contents
- Applications
- Entities
-
Persist Data
- Post
- Put
- Patch
- Delete
- Relationships
- Typed Values
- Schemas
- Getting Entries
-
Queries
- Simple Queries
- Compound Queries
- Pagination
- Introspection
- Type Checking
Applications
const appConfig = {
redirect_uri,
client_id
}
const app = solidApp(appConfig)
const db = app.webID(url)
<!-- or -->
const db = app.provider(URL)
Other applications exist too, for non-SOLID SPARQL providers:
const appConfig = {
endpoint,
username,
password
}
const app = sparqlApp(appConfig)
const db = app()
If we store a local cache to speed up requests, how can that cache (which we can set locally without being authenticated) interact with the remote store in a unauthenticated session?
Entities
Entities are stored as flat objects, that is sets of key:value pairs. Values can be of any given type. Nested objects are created by managing relationships between entities.
Entities have two special keys: @id and @type.
The @id key is used to uniquely identify the entity.
The @type key is used to classify or attach a schema to the entity.
Persist Data
An Entity is identified by its @id. Create a new item \ with db.post.
let ref = await db.post("hummus", {
"@type": "Food",
"ingredient": "Chickpeas and Lemon"
})
ref = {
"@id": "hummus",
"@type": "Food",
"ingredient": "Chickpeas and Lemon"
}
Post
Use db.post to replace an entire entity with a new value:
let ref = await db.post("hummus", {
"@type": "Dish",
"ingredient": "Garbanzo Beans"
})
ref = {
"@id": "hummus",
"@type": "Dish",
"ingredient": "Garbanzo Beans"
}
Put
Replace the value of a given key with db.put.
let ref = await db.put('hummus, {
"ingredient": "Chickpeas",
})
ref = {
"@id": "hummus",
"@type": "Dish",
"ingredient": "Chickpeas"
}
Put and Post are similar but distinct! Post will replace the entire Entity, but Put will replace jut the specified keys.
Patch
Add a value to a key with db.patch
let ref = await db.patch('hummus', {
ingredient: "Lemon",
})
ref = {
"@id": "hummus",
"@type": "Dish",
"ingredient": ["Chickpeas", "Lemon"]
}
Delete
Delete a key/value pair with db.delete
let ref = await db.delete(@id, {
"ingredient": "Lemon",
})
ref = {
"@id": "hummus",
"@type": "Dish",
"ingredient": "Chickpeas"
}
Or delete the entire entity
let ref = await db.delete('hummus')
ref = null
Relationships
Entities can be related to each other.
let hummus = await db.post("hummus", {
"@type": "Dish"
})
let chickpeas = await db.post("chickpeas", {
"@type": "Ingredient",
"aka": "Garbanzo Beans"
})
let ref = await db.put('hummus, {
"ingredient": {
"@id": "chickpeas"
}
})
ref = {
"@id": "hummus",
"@type": "Dish",
"ingredient": "chickpeas"
}
Typed Values
Values can be strings, booleans, and numbers. Types can be specified as well:
db.put("chickpeas", {
"aka": {
@type: String,
value: "Garbs"
}
})
db.put("chickpeas", {
"gramsPerUnit": {
@type: Number,
value: 42
}
})
db.put("chickpeas", {
"vegetarian": {
@type: Number,
value: true
}
})
Values can also be typed as Date, Time, DateTime, URI, Integer, Float, Decimal, Double.
Schemas
Entities can be given schemas with the @type key. A @type can be defined with a schema:
let dishSchema = await db.schema("Dish", {
"name": String,
"ingredient": ID
})
let ingredientSchema = await db.schema("Ingredient", {
"name": String,
"aka": String,
"vegetarian": Boolean
})
Keys can also be given data:
let ref = await db.post("vegetarian", {
"description": "Does this ingredient made entirely from non-animal products?"
})
Okay but _why_ would you do this?
Keys can also be given schemas:
let vegSchema = await db.schema('vegetarian', {
"description": String,
"@domain": "Ingredient",
"@range": Boolean
})
See above. Does this enable static type checking by surfacing errors? Can we use this to do introspection?
Getting Entities
Get a single item by @id:
let ref = await db.get('hummus')
ref = {
"@id": "hummus",
"@type": "Dish",
"name": "Hummus",
"ingredient": ["chickpeas", "lemon"]
}
Get a single item with specified keys:
let ref = await db.get('hummus', {
"ingredient": ""
})
ref = {
"@id": "hummus",
"ingredient": ["chickpeas", "lemon"]
}
Get related items with specified keys:
let ref = await db.get('hummus', {
"ingredient": {
"name": "",
"aka": ""
}
})
ref = {
"@id": "hummus",
"ingredient": [{
"name": "Chickpeas",
"aka": ["Garbanzos", "Garbs"]
},{
"name": "Lemon"
}]
}
Relationships can be reversed:
let ref = await db.get('chickpeas', {
"name": "",
"usedIn": {
"@reverse": "ingredient"
}
})
ref = {
"@id": chickpeas,
"usedIn": "hummus"
}
Queries
Let's say we want to get entities by values other then their @id. How can we do this?
Querying Simple Values
try {
let collection = await db.query(
where(key, operator, value)
)
} catch (e) {
console.error("Error querying the store: ", e);
}
try {
let collection = await db.query(
or (
where(key, operator, value),
where(key, operator, other)
)
)
} catch (e) {
console.error("Error querying the store: ", e);
}