diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..3b375bc --- /dev/null +++ b/.env.local.example @@ -0,0 +1,23 @@ +# Fluree Documentation - Claude Code Environment Variables +# +# These variables enable enhanced documentation verification when using Claude Code. +# They are NOT used by Docusaurus — they're read by Claude Code from your shell environment. +# +# To use: Add these exports to your shell profile (.zshrc, .bashrc, etc.) +# or source this file before starting Claude Code: +# +# source .env.local.example # after filling in values +# +# See CLAUDE.md for full documentation. + +# URL of a running Fluree server for testing documentation examples +# Example: http://localhost:58090 +export FLUREE_TEST_SERVER_URL= + +# Path to local fluree/db repository for source code reference +# Example: /Users/yourname/projects/fluree/db +export FLUREE_DB_REPO_PATH= + +# Path to local fluree/server repository for source code reference +# Example: /Users/yourname/projects/fluree/server +export FLUREE_SERVER_REPO_PATH= diff --git a/.gitignore b/.gitignore index 1f05e6a..48eaea7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ build .docusaurus bin -.DS_Store \ No newline at end of file +.DS_Store + +# Local environment variables (copy .env.local.example to .env.local) +.env.local \ No newline at end of file diff --git a/.tonal-references/working-with-graph-data.mdx b/.tonal-references/working-with-graph-data.mdx new file mode 100644 index 0000000..61eaa79 --- /dev/null +++ b/.tonal-references/working-with-graph-data.mdx @@ -0,0 +1,558 @@ +import Admonition from "@theme/Admonition"; +import { FlureeMermaid } from "@site/src/components/Mermaid/FlureeMermaid.jsx"; + +# Working with Graph Data + +The last chapter focused on how you can describe individual entities using RDF +triples, how those triples form a graph, and how you can use JSON to encode +triples. This chapter refines our notion of "entity" and shows how you can use +JSON to encode relationships between entities. + +Along the way, we'll focus on a problem: How do you represent the following +graph in JSON? How would you insert it into Fluree? + +|"@id"| jid(_:f100) + j -->|name| jn(Jack) + j -->|species| sp1(Mongolian death worm) + j -->|bestFriend| l + l(_:f101) -->|"@id"| lid(_:f101) + l -->|name| ln(Lucia) + l -->|species| sp2(Mongolian death worm) + l -->|bestFriend| j +`} +/> + +This shows two Mongolian death worms named `Jack` and `Lucia` who have a +reciprocal `bestFriend` relationship. JSON on its own has no standard way for +representing this kind of relationship. You'd need some way for `_:f100` to +refer to `_:f101`, and while you could come up with a convention for doing that, +it would be idiosyncratic to your system and difficult to enforce and maintain. + +This chapter will show you how to use JSON-LD, a W3C standard, to represent this +relationship -- and virtually any other graph. It will show you how store these +graphs in Fluree. + +## Representing entities + +Before we enter the realm of JSON-LD, I first need to clarify some subtleties +around how we use the word "entity." Many of us think of an entity as similar to +a _row_ in a relational database or an _object_ in an object oriented language. +An entity has _fields_ (if it's in a db) or _attributes_ (if it's an OO object), +and those fields/attributes have _values_. There's a distinction between the +entity as a data container (row or object), and the data that it contains. + +RDF does not technically support this kind of distinction. It's all just nodes +connected by arcs, resources connected by predicates. Take this graph: + +In the triple `["Jack", "loves", "cheesecake"]`, none of the elements can be +said to have the privileged position of "entity" as something which encompasses +the other elements. This notion is reinforced when you visualize the triple: + +|loves| c(cheesecake) +`} +/> + +There's no container here, just two nodes and an arc. The arc does have a +direction, from Jack to cheesecake, but there's nothing stopping you from +reading this as "cheesecake is loved by Jack" or from adding an arc from +cheesecake to Jack. + +Nevertheless, the notion of an entity as a container is useful and it feels +intuitive. If we're modeling customer data, we want to be able to talk about _a +customer_ as an actual thing that has properties or attributes. The resolution +here is to just acknowledge that you can impose that kind of organization onto +an RDF graph, _but_ it's a convenience for your thinking and communication, and +_not_ something that's technically reflected in the way RDF data is structured. + +We also need to contend with the practical fact that we are representing our +data using JSON objects, which actually _are_ containers. How do we bridge this +gap between RDF's notion of data as a non-hierarchical collection of nodes and +arcs, and JSON's inherently hierarchical structure? + +When we say that a JSON object represents an entity, what we mean is that the +JSON object's key/value pairs correspond to RDF predicates and objects in a set +of triples, and all of those triples have the same subject. + +That gets us part of the way to bridging the gap. But what is the subject? How +do we even specify that? Take this JSON: + +{/* */} + +```json +{ + "name": "Jack", + "species": "Mongolian death worm" +} +``` + +The key/value pairs correspond to RDF predicates and objects, so where would we +even specify a subject? As it turns out, there's a W3C standard that defines how +to fully represent RDF data using JSON data structures, including how to specify +the subject in a JSON object. It's called JSON-LD which stands for _JSON for +Linked Data_. + +## JSON-LD + +JSON-LD is a standard for representing RDF data using JSON data structures. +Because RDF data describes graphs, JSON-LD can also be thought of as a way to +represent graphs using JSON. I'll talk about JSON-LD from both perspectives. + +JSON-LD is just JSON, but with some additional rules for structuring objects to +provide the information machines and humans need to interpret the JSON as a +graph. When I say _a JSON-LD object_, I mean a JSON object that's being +interpreted using the rules of the JSON-LD standard. + +These rules are necessary because JSON on its own doesn't provide us with a +standard way of representing the kinds of relationships we want to represent, +like the mutual `"bestFriend"` relationship between Jack and Lucia from earlier. +With JSON-LD, though, you can represent virtually any graph. + +This is useful in itself; using JSON-LD also gives you a couple strategic +advantages: + +1. Because it's built on top of JSON, you can leverage the wealth of existing + tools for working with JSON data. +2. Because it's a standard, JSON-LD-structured data is portable and composable + in a way that simply isn't possible otherwise. It provides a common language + for representing information across systems, removing the need to constantly + write software that translates between data system. + +In the same way that JSON is a data format that's defined independently from any +particular application, JSON-LD is a way to represent graphs that's defined +independently of any database, including Fluree. Fluree provides a JSON-LD +interface, meaning that the data you insert and update is formatted as JSON-LD, +queries you write (and the results they return) are too. To understand how to use Fluree, we'll need to +learn JSON-LD. + +## Inserting JSON-LD Data + +Let's return to the question of how we indicate the RDF subject in a JSON +object. With JSON-LD, you use the `"@id"` key: + +{/* */} + +```json +{ + "@id": "_:f100", + "name": "Jack", + "species": "Mongolian death worm" +} +``` + + + +In real-world usage you'll want to use _IRIs_ for `"@id"` values, a topic +we'll cover in the next chapter. IRIs typically look like URIs, with values +like `"http://cryptid-research.com/researchers/jack"`. + +We'll keep formatting @id's using the `_:` prefix for the time being, but if +for some reason you stop the tutorial here just be aware that it'll be +important to learn how to work with IRIs. + +For those familiar with RDF, note that Fluree treats blank node identifiers as +stable identifiers, such that if you run multiple transactions using the same +blank node id for `@id`, then it will modify a single subject rather than +inserting new subjects. + + + +This corresponds to the following graph: + +|name| jn(Jack) + j -->|species| s1("Mongolian death worm") +`} +/> + +So that answers the question of how to encode the subject in a JSON object, but +you're probably wondering about where we got the string `"_:f100"`, and what +role Fluree plays in creating these values. After all, in other database systems +there are mechanisms for generating unique primary keys; how does this work in Fluree? + +The answer actually has a surprising amount of depth to it, but for now I'm just +going to focus on the bare minimum you need to know in order to understand how +to represent the `"bestFriend"` relationship: + +1. Because JSON-LD is a strict serialization of RDF triples, and because RDF triples always have a subject, every entity in JSON-LD (and in Fluree) must have an `"@id"` value, even if none is explicitly supplied. +2. You don't need to provide an `"@id"` value explicitly. If you don't, Fluree will generate one for you. These data entities are known as _blank nodes_, and Fluree's `@id` assignment follows a pattern of **`_:f[integer value]`**. +3. If you do provide an `"@id"` value, this value (conventionally, a URI string representing an entity with global uniqueness) becomes an identifier that makes it easier to qualify your updates to (or your queries against) that node in the future. +4. When we transact facts against an entity by referencing its `"@id"` value, those updates effectively qualify _upserts_ against that entity. If an entity with that subject IRI already exists, the facts will be applied to it. If it doesn't exist, that entity will be created. +5. If you include an `"@id"` value in your JSON object, its value does not have + to match Fluree's naming scheme, and its value doesn't need to already exist + in the database. That is to say: you can generate new identifiers using your + own system in order to create new entities in Fluree. + +Let's look at the following transactions (and feel free to try them out directly in the sandbox!) + + + In all of the following examples we will be using Fluree's `insert` key to + assert the following information. We are also able to combine the `insert` key + with the `delete` key to issue more nuanced updates to existing data, and we + can even use the `where` key to bind existing data to `?logicVariables`, + making it possible to execute all kinds of surgical, precise updates to new or + existing data. + + +{/* */} + +```jsonc +// This will insert or update the following facts about +// the entity with the subject IRI, "http://example.org/jack" + +{ + "insert": { + "@id": "http://example.org/jack", + "name": "Jack", + "species": "Mongolian death worm" + } +} +``` + +{/* */} + +```jsonc +// This will insert the same predicate-object pairs as the +// previous transaction, but because no @id is provided, Fluree +// will generate a new blank node entity with an arbitrary @id IRI + +{ + "insert": { + "name": "Jack", + "species": "Mongolian death worm" + } +} +``` + +{/* */} + +```jsonc +// We see here the same predicate-object facts as above, but +// with an entirely new subject IRI, "http://example.org/some-new-identifier". +// Nodes are directly identified by their @id subject IRI, not by other +// predicate-object pairs, so this transaction will not affect or update +// data on "http://example.org/jack" + +{ + "insert": { + "@id": "http://example.org/some-new-identifier", + "name": "Jack", + "species": "Mongolian death worm" + } +} +``` + + + We also have a pattern for updating data on existing entities without using + their `@id` subject IRIs as identifiers. We'll look at this later, but it + involves issuing a kind of subquery with the `where` key to find entities + matching particular data conditions, and then using our `?logicVariable` + patterns to update data on the results of those subqueries. + + +So that's how we represent a single entity with JSON-LD. To represent our best +friend graph, though, we'll need to represent multiple entities. Here's how to +do that: + +{/* */} + +```json +{ + "insert": [ + { + "@id": "_:f100", + "bestFriend": { + "@id": "_:f101" + }, + "name": "Jack", + "species": "Mongolian death worm" + }, + { + "@id": "_:f101", + "bestFriend": { + "@id": "_:f100" + }, + "name": "Lucia", + "species": "Mongolian death worm" + } + ] +} +``` + +With JSON-LD, you can use an array to represent a collection of entities. This +is how we can use JSON to represent any number of nodes and relationships in a +graph. + +`"@id"` is a _keyword_, meaning that JSON-LD assigns them special meaning that's +not present solely in the data itself. `"@id"` is still a JSON key like `"name"` +or any other key, but tools that interpret JSON as JSON-LD (like Fluree) know +that the key has additional significance, designating an identifier that should +be used as the subject for a set of triples. + +This is generally what it means for JSON-LD to be a standard implemented on top +of JSON. JSON-LD defines keywords and their possible values so that applications +will have a clear way of translating JSON into internal data structures. + +Note the value of `"bestFriend"`: it's not simply the string for the `"@id"` +being referenced, it's a JSON object with the key `"@id"`. It's not this: + +```json +{ "bestFriend": "_:f100" } +``` + +It's this: + +```json +{ + "bestFriend": { + "@id": "_:f100" + } +} +``` + +Any time you want to reference an identifier, make sure you do it in this way. + +You can transact multiple entities with Fluree in an array, and Fluree will insert +all entities, meaning that it will create triples for all the key/value pairs in +the JSON object. If you were to transact the JSON with the death worms above, Fluree +would create the following triples: + +{/* */} + +```json +[ + ["_:f100", "@id", "_:f100"], + ["_:f100", "name", "Jack"], + ["_:f100", "species", "Mongolian death worm"], + ["_:f100", "bestFriend", "_:f101"], + ["_:f101", "@id", "_:f101"], + ["_:f101", "name", "Lucia"], + ["_:f101", "species", "Mongolian death worm"], + ["_:f101", "bestFriend", "_:f100"] +] +``` + +Thus, it's possible to insert multiple entities at the same time, and for the +entities to refer to each other. + +## Querying graph data + +Graphs have no logical beginning or end, nor do they have containers in the way +we're used to with other databases. Yet, it's useful to represent them in JSON, +which imposes a bounded, hierarchical structure on the data. We've explored this +from the perspective of transactions but how do we represent and issue queries by using JSON-LD? + +As a reminder from the last chapter, when you execute a query, it's like you're telling Fluree how to: + +1. Filter Fluree's nodes down to some initial set of nodes +2. Recursively include adjacent arcs and nodes +3. Return the selected nodes and arcs as JSON + +### A simple query + +Let's start with a simple query: + +{/* */} + +```json +{ + "select": { + "?s": ["*"] + }, + "where": { + "@id": "?s", + "bestFriend": "?friend" + } +} +``` + +If you've inserted the two Mongolian death worm entities above, you should get a +result that looks like this: + +```json +[ + { + "@id": "_:f101", + "bestFriend": { + "@id": "_:f100" + }, + "name": "Lucia", + "species": "Mongolian death worm" + }, + { + "@id": "_:f100", + "bestFriend": { + "@id": "_:f101" + }, + "name": "Jack", + "species": "Mongolian death worm" + } +] +``` + +What is the relationship between the query and the results? As in the last +chapter, the `"where"` clause is responsible for filtering down Fluree's set of +nodes to those that meet some criteria, and for binding those nodes to a logic +variable. + +Here, the object `{ "@id": "?s", "bestFriend": "?friend" }` selects those nodes that have +any value on the predicate named `"bestFriend"`, regardless of what that value may be. It binds the subject IRIs of those nodes to the `"?s"` logic variable. In our example, the +`"_:f100"` and `"_:f101"` node identifiers get bound to `"?s"`. + + + In this example, we create a `?friend` logic variable in our `where` clause, + but we aren't using the values bound to `?friend` within the `select` + statement where we shape the projection of our query results. Here, it's + simply being used to express that we care about nodes that have any possible + value on the predicate, `bestFriend`, but we don't want to limit our result + set by pre-specifying what that value should be. + + +When we use subject IRI logic variables from our `where` clause to project results via our `select` clause syntax, +we need to tell Fluree how to represent the nodes that are bound to those logic variables. + +We could say, `"select": { "?s": ["*"] }` if we wanted Fluree to crawl every property on the subjects bound to `?s` and represent each property-object value as a JSON key-value pair, or we could be more explicit in the property-object pairs we need returned from this query, for example `"select": { "?s": ["name", "species"] }`, if we only wanted JSON objects returned with the `name` and `species` properties. + +Fluree will construct a JSON object for each of the selected nodes, and it will +use the `"select"` clause to determine which keys and values to include in the +JSON object. + +Let's put this all together. Fluree is storing this graph: + +|"@id"| jid(_:f100) + j -->|name| jn(Jack) + j -->|species| sp1(Mongolian death worm) + j -->|bestFriend| l + l(_:f101) -->|"@id"| lid(_:f101) + l -->|name| ln(Lucia) + l -->|species| sp2(Mongolian death worm) + l -->|bestFriend| j +`} +/> + +The `"where"` clause of your query uses the `?s` logic variable, and given our pattern that looks for any `?s` with any value on the property, `bestFriend`, it binds the `"_:f100"` and `"_:f101"` node IRIs to `?s`, because they are the only nodes with an outgoing arc named `"bestFriend"`. The `"select"` clause specifies +that you want to build a JSON object for each of these nodes such that every +outgoing arc (and its corresponding value) is encoded as key/value pairs in the +JSON object. + +Thus, the query results include two JSON objects, one for each node. Each JSON +object includes the keys `"name"`, `"species"`, and `"bestFriend"` (as well as the JSON key-value pair `@id`, because even though this represents the _subject_ identifier and is not strictly a _property_, JSON has no other way to represent this than with the JSON-LD `@id` key). + +The value of `"bestFriend"` is not just the string `"_:f100"` or `"_:f101"`. +Instead, it's the object `{"@id": "_:f100"}`. `"@id"` values are always encoded +this way. + +### Graph-Crawling in our Select Clauses + +There's a practical reason for this: you might want to populate that object with +more key/value pairs. Check out this query and the result: + +{/* */} + +```json +{ + "select": { + "?s": [ + "name", + { + "bestFriend": ["*"] + } + ] + }, + "where": { + "@id": "?s", + "bestFriend": "?friend" + } +} +``` + +Result: + +```json +[ + { + "name": "Lucia", + "bestFriend": { + "@id": "_:f100", + "name": "Jack", + "species": "Mongolian death worm", + "bestFriend": { + "@id": "_:f101" + } + } + }, + { + "name": "Jack", + "bestFriend": { + "@id": "_:f101", + "name": "Lucia", + "species": "Mongolian death worm", + "bestFriend": { + "@id": "_:f100" + } + } + } +] +``` + +Whereas in the previous queries, we saw the graph data expanded for the `?s` nodes, but not for any additional entities downstream of those nodes, in this query we are explicitly including in our `select` clause that if a property, `bestFriend` exists, and if the value(s) of this property are, themselves, subject identifiers for other nodes, then return all data (`["*"]`) on those downstream nodes. + +The `"select"` clause that yields this result is `["name", {"bestFriend": +["*"]}]`. The `"name"` portion works just as described before: it directs Fluree +to include the `"name"` property-object arc for the selected subject. The object `{"bestFriend": +["*"]}` is where things get interesting. + +Let's call this object a _node object template_. The keys of a node object +template (`"bestFriend"`) correspond to arcs, and the values are arrays that +specify how to build a JSON object for the arcs' nodes (the node that +`"bestFriend"` points to). + +The node object template `{"bestFriend": ["*"]}` is saying, _include the +bestFriend arc. Use a JSON object to represent the node that the bestFriend arc +points to. To construct that JSON object, include all of the node's outgoing +arcs._ + +In my mind, I think of this as Fluree using the arcs you specify to travel to +each node and then running a little recipe to determine how to represent that +node. With the array `["name", {"bestFriend": ["*"]}]`, Fluree travels down the +`"name"` arc to a value node. Because `"name"` is a simple string, the recipe is "just +return the value of this node". Next, Fluree travels down the `"bestFriend"` +arc. Because `"bestFriend"` is specified with a node object template, the recipe +is "construct a JSON object using the array `["*"]`". + +Fluree then travels down each outgoing arc from the `"bestFriend"` node. It +travels down `"name"`, `"species"`, and `"bestFriend"` arcs, and this +time the recipe is "just return the value of this node." + +### Data in the shape you need it + +Fluree's combination of graph data and flexible querying brings a benefit that +isn't obvious: it's a lot easier to get data in the shape that your application +needs it, rather than having to go through extra processing steps to translate +between the data structures your database returns and the ones your app actually +needs. For more examples of data in different shapes and more complex queries, +see [the reference documentation for the Fluree Query Syntax](/docs/reference/flureeql-query-syntax/). + +## Summary + +- [JSON-LD is a W3C standard](https://www.w3.org/TR/json-ld/) that defines how + to represent RDF graphs using JSON +- JSON-LD keywords are JSON keys that are given special meaning in a JSON-LD + context +- You use the `"@id"` key to signify the _subject_ for a JSON-LD object +- You use arrays to encode multiple JSON-LD entities +- Fluree queries specify: + 1. How to filter Fluree's nodes down to some initial set of nodes + 2. Which nodes and arcs to include in the JSON that's returned + 3. How to transform the selected nodes and arcs into JSON + +{/* */} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5ae95db --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,297 @@ +# Fluree Developers Site + +Documentation site for Fluree built with Docusaurus 3.9.2. + +## Project Structure + +``` +docs/ +├── getting-started/ # Quick start, installation, core concepts +├── learn/ # Conceptual documentation +│ ├── foundations/ # Core concepts (graph data, JSON-LD, context) +│ ├── working-with-data/ # CRUD operations, queries, time travel +│ ├── modeling/ # Classes, properties, ontologies, SHACL, reasoning +│ ├── security/ # Access control, policies, verifiable data +│ └── advanced/ # Architecture, language tags, etc. +├── reference/ # API and syntax reference +│ ├── http-api/ # HTTP endpoint documentation +│ ├── querying/ # FlureeQL query syntax (split by topic) +│ └── cookbook/ # Examples and patterns +├── examples/ # Example datasets and walkthroughs +├── cloud/ # Fluree Cloud platform docs +└── sense/ # Fluree Sense product docs (separate product) +``` + +## Tone & Voice + +Documentation should be **warm, enthusiastic, and welcoming** — inviting users to learn the material rather than lecturing at them. Think of the tone in *Clojure for the Brave and True* by Daniel Higginbotham as inspiration. + +### Guidelines + +- **Explain the "why"** — Don't just show syntax; help readers understand the reasoning behind design decisions +- **Don't assume RDF/JSON-LD knowledge** — Many users come from SQL backgrounds; meet them where they are +- **Use second person** — "You can query..." not "One can query..." +- **Be encouraging** — Acknowledge when concepts are tricky, then guide through them +- **Keep it conversational** — Avoid dry, academic prose + +### Tonal Reference + +The original tutorial guide by Daniel Higginbotham is preserved at `.tonal-references/working-with-graph-data.mdx` as an example of the desired tone. Key elements: + +- Uses engaging examples (Mongolian death worms named Jack and Lucia) +- Acknowledges complexity without being intimidating +- Builds concepts progressively with clear explanations +- Conversational without being sloppy + +When writing new content, read this file first to calibrate your voice. + +## Documentation Coherence + +When creating or editing documentation: + +1. **Reference existing docs** — Read related guides to ensure consistency in terminology, syntax examples, and explanations +2. **Cross-link generously** — When mentioning topics covered elsewhere, link to them. The docs should form a cohesive, interconnected whole +3. **Build on prior knowledge** — Later guides should reference concepts from earlier ones rather than re-explaining everything +4. **Maintain consistency** — Use the same example patterns, namespace prefixes, and entity names across related guides when it aids understanding + +## Code Example Standards + +### Copy-Paste Readiness + +Code examples should generally be **complete and runnable** — users should be able to copy-paste them directly. This means: + +- Include full `@context` blocks where needed +- Show complete JSON-LD documents, not fragments +- Include the `from` or `ledger` field in queries/transactions + +**Exceptions**: Some guides specifically teach concepts like `@context` or show incremental building of queries. In these cases, incomplete examples are appropriate when they serve the pedagogical goal. + +### Code-Hike Focus Annotations + +This project uses [Code-Hike](https://codehike.org/) for enhanced code blocks. Use the `focus` annotation to highlight specific lines while still showing complete examples: + +```mdx +```json focus=3:5 +{ + "@context": {"ex": "http://example.org/"}, + "@id": "ex:alice", + "ex:name": "Alice", + "ex:age": 30 +} +``` +``` + +This keeps examples copy-paste ready while drawing attention to what matters. Available focus syntax: + +- `focus=3` — Single line +- `focus=3:5` — Line range +- `focus=3,7,9` — Multiple specific lines +- `focus=3:5,8:10` — Multiple ranges + +You can also use inline focus links in explanatory text: `[the @id field](focus://3[3:15])` highlights specific characters. + +## Documentation Conventions + +### File Formats + +- Use `.md` for simple markdown files +- Use `.mdx` for files requiring React components, Code-Hike features, or Mermaid diagrams + +### Code Fence Languages + +- JSON examples: `json` +- Shell/curl examples: `sh` +- SPARQL: `sparql` +- Turtle/RDF: `turtle` +- Clojure: `clojure` +- JavaScript: `javascript` +- Python: `python` + +### API Reference Format + +For HTTP endpoints, follow this structure: + +1. Endpoint heading (`## \`fluree/endpoint\``) +2. HTTP method block (` ```POST /fluree/endpoint``` `) +3. Request Object table (Key | Required | Value) +4. Example Request Object (JSON) +5. Curl Example +6. Example Response + +### Tables + +Use GitHub-flavored markdown tables: + +```md +| Key | Required | Value | +| ---------- | -------- | ------------------------------- | +| `@context` | no | **object** • description | +| `ledger` | yes | **string** • description | +``` + +### Admonitions + +Use blockquotes with **NOTE**, **WARNING**, **TIP** prefixes: + +```md +> **NOTE**: Important information here. +``` + +### Internal Links + +Use relative paths with `/docs/` prefix: + +```md +[Link Text](/docs/reference/http-api) +``` + +### Mermaid Diagrams + +Supported via `@docusaurus/theme-mermaid`. Use in `.mdx` files: + +````md +```mermaid +graph LR + A --> B +``` +```` + +## Sidebar Configuration + +Sidebars are configured in `sidebars.js`. Main sections: + +- `gettingStarted` - Quick start, installation, core concepts +- `learn` - Foundations, Working with Data, Modeling, Security, Advanced +- `reference` - HTTP API, Query syntax, Transaction syntax, Cookbook +- `examples` - Example datasets by domain and feature +- `cloud` - Fluree Cloud docs +- `SenseSidebar` - Fluree Sense docs + +## Quality Checks + +Before finalizing documentation changes: + +### Content Review + +- [ ] Read through the guide as a new user would — does it flow logically? +- [ ] Are all concepts explained before they're used? +- [ ] Are prerequisites clearly stated? +- [ ] Do examples build on each other sensibly? + +### Link Validation + +- [ ] All internal links resolve (not pointing to deleted/moved pages) +- [ ] External links are valid and relevant +- [ ] Anchor links (`#section-name`) point to existing headings + +### Build Verification + +```sh +bun run build +``` + +The build will fail on broken markdown links (`onBrokenLinks: "throw"` in config). Fix any errors before committing. + +### Cross-Reference Check + +- [ ] Related guides link to each other appropriately +- [ ] No orphaned pages (every page reachable from sidebar) +- [ ] New pages added to `sidebars.js` + +## Development + +```sh +bun install # Install dependencies +bun start # Start dev server (hot reload) +bun run build # Production build +``` + +## Verifying Documentation with Claude Code (Optional) + +Contributors using [Claude Code](https://docs.anthropic.com/en/docs/claude-code) can enable enhanced documentation verification. These features are entirely optional — contributors who don't use Claude Code can ignore this section. + +### Testing Code Examples + +Claude can verify that documentation examples actually work by running them against a live Fluree instance. + +**Setup:** +1. Start a Fluree server locally (see [Getting Started](/docs/getting-started/installation)) +2. Set the environment variable: + ```sh + export FLUREE_TEST_SERVER_URL="http://localhost:58090" + ``` +3. Ask Claude to verify examples: *"Can you test the examples in this guide against Fluree?"* + +**Behavior:** +- If `FLUREE_TEST_SERVER_URL` is set, Claude will run transactions and queries via curl and report whether they succeed +- If not set, Claude will note: *"I can verify these examples if you start a Fluree server and set `FLUREE_TEST_SERVER_URL`"* + +### Exploring Fluree Source Code + +Claude can reference the `fluree/db` and `fluree/server` repositories to verify that documentation matches actual implementation. + +**Option 1: Local repositories** (if you have them cloned) + +Set environment variables pointing to your local clones: + +```sh +export FLUREE_DB_REPO_PATH="/path/to/fluree/db" +export FLUREE_SERVER_REPO_PATH="/path/to/fluree/server" +``` + +Claude will use its built-in file tools to search and read code directly. + +**Option 2: GitHub MCP Server** (no local clone needed) + +If you don't want to clone the repos, you can add the [GitHub MCP Server](https://github.com/github/github-mcp-server) to your Claude Code configuration. This lets Claude search and read code directly from GitHub. + +Add to your Claude Code MCP settings: + +```json +{ + "mcpServers": { + "github": { + "command": "docker", + "args": [ + "run", "-i", "--rm", + "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "" + } + } + } +} +``` + +> **NOTE**: A GitHub Personal Access Token is optional for public repos but recommended to avoid rate limits. Create one at https://github.com/settings/personal-access-tokens with `repo` scope. + +### Environment Variable Summary + +| Variable | Purpose | +|----------|---------| +| `FLUREE_TEST_SERVER_URL` | URL of running Fluree server for testing examples | +| `FLUREE_DB_REPO_PATH` | Path to local `fluree/db` clone for source reference | +| `FLUREE_SERVER_REPO_PATH` | Path to local `fluree/server` clone for source reference | + +### Quick Setup + +For contributors who want the full experience: + +```sh +# Add to your shell profile (.zshrc, .bashrc, etc.) +export FLUREE_TEST_SERVER_URL="http://localhost:58090" +export FLUREE_DB_REPO_PATH="/path/to/fluree/db" +export FLUREE_SERVER_REPO_PATH="/path/to/fluree/server" +``` + +Or copy and fill in `.env.local.example`, then source it: + +```sh +cp .env.local.example .env.local +# Edit .env.local with your values +source .env.local +``` + +Then when working on docs, start Fluree and ask Claude to verify your examples. diff --git a/docusaurus.config.js b/docusaurus.config.js index b7d0035..2d100e5 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -62,17 +62,24 @@ module.exports = { position: "left", className: "navbar-home-link", }, + { + label: "Getting Started", + to: "/getting-started", + docId: "getting-started/index", + type: "doc", + position: "left", + }, { label: "Learn", to: "/learn", - docId: "learn/overview", + docId: "learn/index", type: "doc", position: "left", }, { label: "Reference", - to: "/refence", - docId: "reference/http-api", + to: "/reference", + docId: "reference/http-api/index", type: "doc", position: "left", }, @@ -143,8 +150,8 @@ module.exports = { title: "Docs", items: [ { - label: "Tutorial", - to: "docs/learn/tutorial/introduction/", + label: "Learn", + to: "/docs/learn/", }, { label: "Examples", @@ -243,12 +250,13 @@ module.exports = { ], ], }, - gtag: { - // You can also use your "G-" Measurement ID here. - trackingID: "G-XDLKYE388T", - // Optional fields. - anonymizeIP: true, // Should IPs be anonymized? - }, + gtag: + process.env.NODE_ENV === "production" + ? { + trackingID: "G-XDLKYE388T", + anonymizeIP: true, + } + : false, // blog: { // showReadingTime: true, // // TODO: Please change this to your repo. diff --git a/package.json b/package.json index 747280e..8856904 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", + "start": "docusaurus start --port 3210", "build": "docusaurus build", "dev": "docusaurus start --port 3001 --no-open", "swizzle": "docusaurus swizzle", diff --git a/sidebars.js b/sidebars.js index 7befd21..b6e6267 100644 --- a/sidebars.js +++ b/sidebars.js @@ -456,53 +456,285 @@ module.exports = { ] } ], + "getting-started": [ + "getting-started/index", + "getting-started/quick-start", + "getting-started/installation", + "getting-started/core-concepts", + ], learn: [ - "learn/overview", + "learn/index", { - Tutorial: [ - "learn/tutorial/introduction", - "learn/tutorial/fluree-data-model", - "learn/tutorial/working-with-graph-data", - "learn/tutorial/collaborative-data", + type: "category", + label: "Foundations", + items: [ + "learn/foundations/what-makes-fluree-different", + "learn/foundations/how-fluree-stores-data", + "learn/foundations/understanding-graph-relationships", + "learn/foundations/from-tables-to-graphs", + "learn/foundations/json-ld", + "learn/foundations/understanding-context", ], }, { - Guides: [ - "learn/guides/working-with-context", - "learn/guides/working-with-ontologies", + type: "category", + label: "Working with Data", + items: [ + "learn/working-with-data/inserting-data", + "learn/working-with-data/updating-and-deleting-data", + "learn/working-with-data/querying-basics", + "learn/working-with-data/advanced-queries", + "learn/working-with-data/context-patterns", + "learn/working-with-data/time-travel-and-history", ], }, { - Foundations: [ - "learn/foundations/json-ld", - "learn/foundations/querying", - "learn/foundations/data-access-control", - "learn/foundations/verifiable-data", - "learn/foundations/semantic-vocabularies", - "learn/foundations/architecture-overview", - "learn/foundations/from-tables-to-graphs", + type: "category", + label: "Modeling Your Data", + items: [ + "learn/modeling/classes-and-properties", + "learn/modeling/building-ontologies", + "learn/modeling/working-with-ontologies", + "learn/modeling/shacl-validation", + "learn/modeling/semantic-vocabularies", + "learn/modeling/introduction-to-reasoning", + ], + }, + { + type: "category", + label: "Security & Access Control", + items: [ + "learn/security/data-access-control-concepts", + "learn/security/writing-policies", + "learn/security/verifiable-data", + ], + }, + { + type: "category", + label: "Advanced Topics", + items: [ + "learn/advanced/calculated-transactions", + "learn/advanced/language-tags", + "learn/advanced/architecture-overview", + "learn/advanced/collaborative-data", ], }, ], examples: [ + "examples/home", + { + type: "category", + label: "Domain Examples", + items: [ + { + type: "category", + label: "E-Commerce Catalog", + items: [ + "examples/datasets/ecommerce/introduction", + "examples/datasets/ecommerce/querying", + "examples/datasets/ecommerce/transactions", + ], + }, + { + type: "category", + label: "Org Chart", + items: [ + "examples/datasets/org-chart/introduction", + "examples/datasets/org-chart/querying", + "examples/datasets/org-chart/reasoning", + ], + }, + { + type: "category", + label: "Knowledge Base", + items: [ + "examples/datasets/knowledge-base/introduction", + "examples/datasets/knowledge-base/querying", + "examples/datasets/knowledge-base/history", + ], + }, + { + type: "category", + label: "Healthcare Records", + items: [ + "examples/datasets/healthcare/introduction", + "examples/datasets/healthcare/querying", + "examples/datasets/healthcare/policies", + ], + }, + { + type: "category", + label: "Supply Chain", + items: [ + "examples/datasets/supply-chain/introduction", + "examples/datasets/supply-chain/querying", + "examples/datasets/supply-chain/history", + ], + }, + ], + }, + { + type: "category", + label: "Feature Showcases", + items: [ + { + type: "category", + label: "Policy & Access Control", + items: [ + "examples/datasets/access-control/introduction", + "examples/datasets/access-control/defining-policies", + "examples/datasets/access-control/testing-policies", + ], + }, + { + type: "category", + label: "Reasoning & Inference", + items: [ + "examples/datasets/reasoning/introduction", + "examples/datasets/reasoning/rules", + "examples/datasets/reasoning/querying-inferred", + ], + }, + { + type: "category", + label: "Full-Text Search", + items: [ + "examples/datasets/full-text-search/introduction", + "examples/datasets/full-text-search/search-queries", + ], + }, + { + type: "category", + label: "SHACL Validation", + items: [ + "examples/datasets/shacl-validation/introduction", + "examples/datasets/shacl-validation/defining-shapes", + "examples/datasets/shacl-validation/validation-errors", + ], + }, + { + type: "category", + label: "Time-Travel & Audit", + items: [ + "examples/datasets/time-travel/introduction", + "examples/datasets/time-travel/history-queries", + ], + }, + ], + }, { - "Sharing Secure, Trusted Data": [ + type: "category", + label: "Integration Guides", + items: [ + { + type: "category", + label: "SQL Migration", + items: [ + "examples/datasets/sql-migration/introduction", + "examples/datasets/sql-migration/schema-mapping", + "examples/datasets/sql-migration/query-comparison", + ], + }, + { + type: "category", + label: "REST API Patterns", + items: [ + "examples/datasets/rest-api/introduction", + "examples/datasets/rest-api/crud-operations", + "examples/datasets/rest-api/advanced-patterns", + ], + }, + ], + }, + { + type: "category", + label: "Academic Credentials", + items: [ "examples/datasets/academic-credentials/introduction", "examples/datasets/academic-credentials/querying", ], }, ], reference: [ - "reference/http-api", - "reference/cloud-http-api", - "reference/fluree-server-config", - "reference/flureeql-query-syntax", - "reference/history-syntax", - "reference/transaction-syntax", - "reference/cookbook", + { + type: "category", + label: "HTTP API", + items: [ + "reference/http-api/index", + "reference/http-api/ledger-operations", + "reference/http-api/transactions", + "reference/http-api/queries", + "reference/http-api/real-time", + "reference/http-api/remote", + ], + }, + { + type: "category", + label: "Querying", + items: [ + { + type: "category", + label: "FlureeQL", + items: [ + "reference/querying/index", + "reference/querying/select-clauses", + "reference/querying/where-clauses", + "reference/querying/aggregations", + "reference/querying/modifiers", + "reference/querying/advanced-features", + ], + }, + "reference/full-text-search", + "reference/history-syntax", + ], + }, + { + type: "category", + label: "SPARQL", + items: [ + "reference/sparql-syntax", + ], + }, + { + type: "category", + label: "Transactions", + items: [ + "reference/transaction-syntax", + "reference/data-types", + ], + }, + { + type: "category", + label: "Schema & Validation", + items: [ + "reference/shacl-validation", + "reference/reasoning", + ], + }, + { + type: "category", + label: "Security", + items: [ + "reference/authentication", + "reference/policy-syntax", + ], + }, + { + type: "category", + label: "Configuration", + items: [ + "reference/fluree-server-config", + ], + }, + { + type: "category", + label: "Cookbook", + items: [ + "reference/cookbook/index", + "reference/cookbook/examples", + ], + }, "reference/error-codes", - "reference/policy-syntax", - "reference/data-types", ], cloud: [ "cloud/welcome", @@ -584,6 +816,13 @@ module.exports = { }, ], }, + { + type: "category", + label: "API Reference", + items: [ + "reference/cloud-http-api", + ], + }, ] }; \ No newline at end of file diff --git a/src/pages/index.jsx b/src/pages/index.jsx index b9f5f26..7eecc01 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -33,7 +33,7 @@ const HomePage = () => {

Get started @@ -52,61 +52,53 @@ const HomePage = () => {
-

Learn

+

Getting Started

- Fluree Overview + Quick Start

- What sets Fluree apart and how it can help you + Create a ledger, insert data, and run queries in 5 minutes
-

- Tutorial + Installation

- Learn the essentials of using Fluree and develop an accurate - mental model of the system + Docker, JAR deployment, and configuration options
- {/* */}

- Guides + Core Concepts

- Get into the details of working with Open Standards and Fluree + Understand ledgers, entities, JSON-LD, and time travel
- {/* +
+
+ +

Learn

+
+
+
+

+ Learn Fluree +

+
+ Concepts, guides, and advanced topics for mastering Fluree
- */}

Foundations

- Deep dives into slices of Fluree functionality + Deep dives into JSON-LD, querying, policies, and architecture
{/*