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
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()
Or even local-first solutions for keeping all data on-device:
const app = localApp()
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?
Assme something like, SPARQL endpoint or Local storage; we can persist multiple graphs seperate from each other as named graphs. Do we want to do that by default? Do we want to segregate them by db id or app id?
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": boolean,
"@value": true
}
})
Values can also be typed as date
, time
, datTime
, uri
, integer
, float
, decimal
, or 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
with db.get
let ref = await db.get('hummus')
ref = {
"@id": "hummus",
"@type": "Dish",
"name": "Hummus",
"ingredient": ["chickpeas", "lemon"]
}
Get Data Shapes
Get a single item with specified keys:
let ref = await db.get('hummus', {
"ingredient": ""
})
ref = {
"@id": "hummus",
"ingredient": ["chickpeas", "lemon"]
}
Get Relationships
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"
}]
}
Get Reverse Relationships
Relationships can be reversed:
let ref = await db.get('chickpeas', {
"name": "",
"usedIn": {
"@reverse": "ingredient"
}
})
ref = {
"@id": chickpeas,
"usedIn": "hummus"
}