Loading...
Loading...
Make a canister's data queryable by the Caffeine Data Intelligence agent. Use whenever an app stores structured data (Maps/Lists/arrays of records) that should be answerable in natural language — "top customers", "revenue by region", "active projects". Adds a discoverable `schema()` and a JSON `execute()` query endpoint via the `caffeineai-oql` mops package's `Expose` mixin.
npx skill4agent add caffeinelabs/skills extension-oqlinclude Expose({ ... })shared queryschema(token : ?Text) : Textexecute(qJson : Text, token : ?Text) : ResultoqlMintTokenoqlRevokeTokenshared querygetCustomersInGermany()getOrdersForCustomer(id)mops add caffeineai-oql@0.1.0mo:caffeineai-oql/...moc 1.9.0--default-persistent-actors --implicit-package=coremoc 1.9.0__record.toEntity(name, typeName, primaryKey)Exposesharedimport Map "mo:core/Map";
import Nat "mo:core/Nat";
import OQL "mo:caffeineai-oql";
import Expose "mo:caffeineai-oql/Expose";
actor {
type Customer = { id : Nat; name : Text; country : Text; monthlyRevenueUsd : Nat };
type Order = { id : Nat; customerId : Nat; amountUsd : Nat; paid : Bool };
let customers = Map.empty<Nat, Customer>();
let orders = Map.empty<Nat, Order>();
include Expose({
entities = [
customers.toEntity("customer", "Customer", "id")
.sample({ id = 0; name = ""; country = ""; monthlyRevenueUsd = 0 })
.build(),
orders.toEntity("order", "Order", "id")
.sample({ id = 0; customerId = 0; amountUsd = 0; paid = false })
// `customerId` points at the customer entity's primary key:
.edge("customerId", "customer")
.build(),
];
// Access is decided by three functions. controllerOnly lets the platform
// (a controller) read every row; everyone else is denied. isPublic stays
// false so the data is not world-readable, and there is no token scheme.
isPublic = func () : Bool = false;
authorizeUser = OQL.Auth.controllerOnly;
authorizeToken = OQL.Auth.noExternalTokens;
});
public shared func addCustomer(c : Customer) : async () { customers.add(c.id, c) };
public shared func addOrder(o : Order) : async () { orders.add(o.id, o) };
};Accesspublic type Access = { #deny; #unrestricted; #scoped : Principal };#deny#unrestricted#scoped p#ownerp#denyisPublic() -- the whole surface is open (#unrestricted)
authorizeUser(caller) -- principal-based policy
<minted token> -- bearer token from oqlMintToken; its owner is the scope
authorizeToken(token) -- your own token scheme| Config field | Shape | Presets in |
|---|---|---|
| | write the literal ( |
| | |
| | |
schema()execute()isPublic = func () : Bool = falseauthorizeUser = OQL.Auth.controllerOnlyauthorizeToken = OQL.Auth.noExternalTokensisPublic = func () : Bool = trueAccesslet admins : Set.Set<Principal> = Set.empty();
authorizeUser = func (p : Principal) : OQL.Auth.Access =
if (p.isController() or admins.contains(p)) #unrestricted
else if (p.isAnonymous()) #deny
else #scoped p; // every other signed-in user sees only their own rowsoqlMintToken(owner : ?Principal, ttlSeconds : ?Nat)oqlRevokeToken(token)transientowner = ?ppowner = nullopt textschemaexecuteauthorizeToken.ownedBy(field)#scoped ppstartnull#unrestrictedauthorizeUser#scoped callerselfScopedoqlMintToken(?p, ...)Map<Principal, List<T>>OQL.Entity.newScoped(name, scopedIter, typeName, primaryKey)scopedIternull.sample.ownedBy(field).ownedByWith(field, canSee)canSee : (Principal, OQL.Value) -> Bool// `allowedUsers : Map<Principal, ()>` is your own actor state.
docs.toEntity("doc", "Doc", "id")
.ownedByWith("owner", func (caller, owner) =
allowedUsers.get(caller) != null // listed → see everything
or owner == #text(caller.toText())) // otherwise → only your own
.build()"owner"schema()#unrestricted.ownedBy(field).ownedByWith(field, OQL.Entity.ownerIsCaller)T.toEntity_toRowNatIntFloatTextBoolNat8/16/32/64Int8/16/32/64Principalcustomers.toEntity(name, typeName, primaryKey)
.sample(template) // optional, see below
.edge(fieldName, targetEntity) // re-tag an auto-derived field as FK
.ownedBy(fieldName) // owner column → per-user scoping
.ownedByWith(fieldName, canSee) // ...with an app-defined predicate
.domain(fieldName, [values]) // declare a field's allowed values
.hidden(fieldName) // drop a field from the schema
.build()customers.toEntity(...)OQL.Entity.new<Customer>(name, func () = customers.values(), typeName, primaryKey).toEntityMapSetList[T][var T].edge(name, target)target"<name>.<targetField>".hiddenTextNatIntBoolFloat.domain(name, values)schema()valuesvalues[OQL.Value][#text("draft"), #text("published")].ownedBy(name).ownedByWith(name, canSee).edge.hidden.hidden(name)schema()select.sample(template)build()[]T__recordselect.toEntityManualOQL.Entity.manualTarticles.toEntityManual<Article>(name, typeName, primaryKey)
.payload(fieldName, extract : T -> V) // V picks a _toRow instance
.flatten(extract : T -> S) // splice a nested record's fields in
.domain (fieldName, [values]) // declare a field's allowed values
.edge (fieldName, targetEntity) // tags a declared payload as FK
.hidden (fieldName) // drops a declared payload
.build().payload(name, extract)extractV_toRowTextNat..flatten(extract).payloadextract : T -> SSS.hidden(name).edge.hidden.payload.flattenOQL.Entity.manual<T>(name, iter, typeName, primaryKey)// Author = { id : Nat; name : Text; address : Address; tags : [Text] }
// Address = { city : Text; country : Text; postalCode : Text }
authors.toEntityManual<Author>("author", "Author", "id")
.payload("id", func a = a.id)
.payload("name", func a = a.name)
.flatten(func a = a.address) // → city, country, postalCode columns
.payload("tagCount", func a = a.tags.size())
.build()__1__2OQL.Value{ #null_; #bool : Bool; #nat : Nat; #int : Int; #float : Float; #text : Text }_toRowPrincipal#text#nat#int#floatFloatRow type | Mode |
|---|---|
| Record of primitives only | auto-derive ( |
Record with | auto-derive once you ship |
Record with nested record ( | auto once you ship |
| Record with a variant field | auto once you ship |
Record with a collection field ( | manual — |
| Tuple, primitive, or anything else | manual — |
_toRow_toRow : T -> OQL.Value<TypeName>Value.mopublic func _toRow(self : T) : OQL.Value.toEntity(...).payload// DepartmentValue.mo — nested record → child's primary key
import OQL "mo:caffeineai-oql";
import Types "Types";
module {
public func _toRow(self : Types.Department) : OQL.Value = #text(self.name);
};department : Departmentdepartment : Text.edge("department", "department")// OptTextValue.mo — optional field → pick a sentinel
import OQL "mo:caffeineai-oql";
module {
public func _toRow(self : ?Text) : OQL.Value = switch self {
case null { #text("") };
case (?t) { #text(t) };
};
};Value_toRow#null_#text{ "eq": { "field": "x", "value": "" } }?Nat?Bool// StatusValue.mo — variant → per-tag text
module {
public func _toRow(self : Status) : OQL.Value = #text(switch self {
case (#draft) { "draft" };
case (#published) { "published" };
case (#archived) { "archived" };
});
};T_toRow_toRow : T -> Row_toRow : T -> ValueRowValueTypeValue.mo.payloadcustomer.payload// metrics : Map<Int (bucket), Map<Nat (sensor), Measurement>>
type MeasurementRow = { bucket : Int; sensor : Nat; metric : Text; value : Nat; okay : Bool };
OQL.Entity.new<MeasurementRow>(
"measurement", func () = flattenMetrics(metrics), "MeasurementRow", "sensor",
)
.edge("bucket", "bucket")
.edge("sensor", "sensor")
.build()(Int, Nat, Measurement)Map<Entity, _>// articlesByAuthor : Map<Author, List<Article>> already exists
OQL.Entity.manual<Author>("author", func () = articlesByAuthor.keys(), "Author", "id")
.payload("id", func a = a.id)
.payload("name", func a = a.name)
.flatten(func a = a.address)
.build()// Article = { id : Nat; tags : [Text]; ... }
OQL.Entity.manual<(Article, Text)>(
"articleTag", func () = flattenArticleTags(articles), "Pair", "pair",
)
.payload("article", func ((a, _)) = a.id)
.edge ("article", "article")
.payload("tag", func ((_, t)) = t)
.edge ("tag", "tag")
.build().edge(name, target).payloadValuefunc optText(o : ?Text) : Text = switch o { case null ""; case (?t) t };
func optNat(o : ?Nat) : Nat = switch o { case null 0; case (?n) n };
func statusText(s : Status) : Text = switch s {
case (#draft) "draft"; case (#published) "published"; case (#archived) "archived";
};
func tagSummary(tags : [Text]) : Text = Text.join(tags.values(), ",");.payload("terminationDate", func e = optText(e.terminationDate))
.payload("status", func a = statusText(a.status))
.payload("tagCount", func a = a.tags.size())
.payload("tagSummary", func a = tagSummary(a.tags))<TypeName>Value.mo"customerId.country"execute()inexecuteshared querysharedexecuteMap.flatten_toRow<TypeName>Value.moexecuteshared queryO(n log n)orderByoffsetlimitfunc () = collection.values().toEntity_toRow.toEntityManualOQL.Entity.manual<TypeName>Value.mo.sample(template).payload.flatten(func x = x.sub).edge(name, targetEntity).hidden(name)Value#null_#text.ownedBy(field).ownedByWith(field, canSee).build()entities = [...]schemaexecutemops build