From 41556817c2ec7f646746d42e2ff3551bf0425cbf Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Mon, 9 Dec 2024 17:57:08 +0100 Subject: [PATCH 1/5] add schema and mapping file doc --- docs/schema.md | 473 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 docs/schema.md diff --git a/docs/schema.md b/docs/schema.md new file mode 100644 index 00000000..b6ae6a8f --- /dev/null +++ b/docs/schema.md @@ -0,0 +1,473 @@ +## Use Cases + +This section describes the different use cases for schema development including interaction with the public schema. + +### Initial development on an existing schema + +1. Run sync via a CLI command to create a local schema incl. mappings + +### Initial development on a fresh schema and publish it + +1. Define a schema (without mappings) +2. Build with it locally +3. Publish + 1. Generate new IDs for the Entity & Relation types + 2. Show the types that would be generated and ask for confirmation + 3. Invoke API to publish the new Schema changes. Depending on the approval process this might now happen immediately or we need to wait for approval. + +### Update a schema + +1. Update you local schema - mapping might be missing +2. Build with it +3. Publish + 1. Check for if the public schema has matching types where the name and type matches e.g. `age: number`. If yes, pick these. If not the create new IDs + 2. Show a diff of the changes and ask for confirmation + 3. Invoke API to publish the new Schema changes. Depending on the approval process this might now happen immediately or we need to wait for approval. + +### Sync from public schema without local changes + +1. Invoke sync via a CLI command +2. Show a diff of the changes which fields & mappings would be added based on the public schema and ask for confirmation +3. Apply the changes + +### Sync from public schema with local changes + +1. Invoke sync via a CLI command +2. Try to match new mappings to fields based on name and type. If there there is a match to a new local field use this one and only create a mapping, if not create new fields and mappings. +3. Show a diff of the changes and ask for confirmation +4. Apply the changes + +## Rules + +- Each entity can have multiple types. +- All fields can be undefined +- A relation is only valid of a from, to and index exists. +- All relations are many to many. They can have a hint with the `one max` field about the cardinality. +- Relation can have additional fields. + +## Open Questions + +- Can system properties also be undefined e.g. `Created by`? What to do in such a case? +- Does every mapping include the space ID as well or only the type ID, attribute ID and relation ID? This depends of we want sync entities and relations from only one or multiple spaces. +- Can a local schema type/field map to multiple public schema types/fields? e.g. local `User` maps to the public schema type `User` and `Person`. Therefor the email field should be mapped to the attribute `email` of the `User` type and the attribute `email` of the `Person` type which both might have the same ID, but can be two different fields as well. +- What to do in case there are conflicting attributes e.g. `Location` and `Sensor` both have an `temperature` field and one is a number and the other a string. Do we merge them or how to map them to different fields? The most reasonable solution I can think of is to map them manually in the mappings file. + +## Schema Design + +- Relations are defined directly on the entity type (see an alternative Design below) +- We want immediate feedback on invalid relations. +- Handling invalid Relations + - In case the from or to is missing we ignore the relation completely. + - In case the index is missing, we set an index at the end of the list. Later we can provide a callback to choose different behavior. + - In case the index is not unique we pick one of them and move it between this and the next item. + +### Design A + +An object based schema definition where relations must be defined on the entity type. This design assumes that the local schema doesn't deal with entities having multiple types. This fact is only dealt with in the mappings file and therefor the local schema stays a lot simpler. + +```ts +export const schema = { + User: { + name: type.Text, + email: type.Text, + ownedEvents: type.Relation({ + types: 'Event', + }), + }, + Event: { + name: type.Text, + owners: type.Relation({ + types: 'User', + }), + }, +}; +``` + +Relations can be named to allow for multiple relations to the same type. + +```ts +export const schema = { + User: { + name: type.Text, + email: type.Text, + ownedEvents: type.Relation({ + map: 'user_owned_events', + types: 'Event', + }), + participatingEvents: type.Relation({ + map: 'event_participants', + types: 'Event', + }), + }, + Event: { + name: type.Text, + owners: type.Relation({ + map: 'user_owned_events', + types: 'User', + }), + participants: type.Relation({ + map: 'event_participants', + types: 'User', + }), + }, +}; +``` + +For this design I haven't explored if we can show TypeScript type errors for invalid relations. If we want to go down this route this would be a good thing to explore next. In any case if relations don't match then the schema should be invalid. + +### Design B + +Also an object based schema definition where relations are defined separately from the entity types. + +```ts +export const schema = { + types: { + User: { + name: type.Text, + email: type.Text, + }, + Event: { + name: type.Text, + }, + }, + relations: { + ownedEvents: { + from: 'User', + to: 'Event', + } + }, +}; +``` + +In case of a conflict we can use the `map` field to define the relation name. + +```ts +export const schema = { + types: { + ... + }, + relations: { + ownedEvents: { + from: 'User', + to: 'Event', + map: 'user_owned_events', + }, + participatingEvents: { + from: 'Event', + to: 'User', + map: 'event_participants', + }, + }, +}; +``` + +### Design C + +While I have very little idea if typechecking is possible on relationships in this design and I'm personally not a big fan of class based schemas I wanted to add it for the sake of completeness. + +```ts +import { Entity, Field, Relationship } from '@graph-protocol/graph-framework'; + +@Entity('Person') +class Person { + @Attribute() + name: string; + + @Attribute() + age: number; + + @Relationship('worksAt') + employers: Organization[]; +} + +@Entity('Organization') +class Organization { + @Attribute() + name: string; + + @Relationship('worksAt') + employees: Person[]; +} +``` + +## Mappings + +- Mappings are optional. +- When syncing from public schema we must match the type ID, attribute ID and relation ID. + +### Design 1 + +In this design we can map a local entity to multiple public schema types. We only define the attributes that are mapped to the public schema. + +We indicate which local type maps to which public schema type with the `types` field. +In addition we define the attributes that are mapped to the public schema. Each attribute maps to a space ID, a type ID and an attribute ID to clearly identify the attribute. + +```ts +const mappings = { + 'User': { + types: ["xyz", "wzx"], // these are the IDs of the types the local entity represents. xyz is `Person` and wzx is `User` + attributes: { + name: [{ + spaceId: 'abc', + typeId: 'xyz', // public schema type: `Person` + attributeId: 'asd', // attribute `name: string` on the public schema with the ID `asd` + }], + age: [{ + spaceId: 'abc', + typeId: 'xyz', // public schema type: `Person` + attributeId: 'gfd', // attribute `age: number` on the public schema with the ID `gfd` + }], + } + } +} +``` + +Relations are defined next to the attributes. + +```ts +const mappings = { + 'User': { + types: ["xyz", "wzx"], // these are the IDs of the types the local entity represents. xyz is `Person` and wzx is `User` + attributes: { + ... + }, + relations: { + attendingEvents: [{ + spaceId: 'abc', + relationId: 'asd' + }], + authoredEvents: [{ + spaceId: 'abc', + relationId: 'qwe' + }] + } + } +} +``` + +#### Handling Special Cases + +How to handle mappings that should be merged? In this example the `name` attribute should be mapped to the `Person` and `User` type since they have the same attribute ID on both types. + +```ts +const mappings = { + 'User': { + id: "abc", + types: ["xyz", "wzx"], // these are the IDs of the types the local entity represents. xyz is `Person` and wzx is `User` + attributes: { + name: [{ + spaceId: 'abc', + typeId: 'xyz', // public schema type: `Person` + attributeId: 'asd', + }, + { + spaceId: 'abc', + typeId: 'wzx', // public schema type: `User` + attributeId: 'asd', + }], + } + } +} +``` + +Here the situation in case they have the same name, same type, but different attribute IDs. + +```ts +const mappings = { + 'User': { + id: "abc", + types: ["xyz", "wzx"], // these are the IDs of the types the local entity represents. xyz is `Person` and wzx is `User` + attributes: { + name: [{ + spaceId: 'abc', + typeId: 'xyz', // public schema type: `Person` + attributeId: 'asd', + }, + { + spaceId: 'abc', + typeId: 'wzx', // public schema type: `User` + attributeId: 'lal', // this is a different attribute ID than the one on the `Person` type + }], + } + } +} +``` + +How to handle mappings which are conflicting? e.g. +- attribute `name` on `Person` is a string (id: `asd`) and +- attribute `name` on `User` is a number (id: `lal`). + +```ts +const mappings = { + 'User': { + id: "abc", + types: ["xyz", "wzx"], // these are the IDs of the types the local entity represents. xyz is `Person` and wzx is `User` + attributes: { + name: [{ + spaceId: 'abc', + typeId: 'xyz', // public schema type: `Person` + attributeId: 'asd', + }], + nameOnUser: [{ + spaceId: 'abc', + typeId: 'wzx', // public schema type: `User` + attributeId: 'lal', + }], + } + } +} +``` + +Also local relations can be mapped to multiple public schema relations: + +```ts +const mappings = { + 'User': { + types: ["xyz", "wzx"], + attributes: { + ... + }, + relations: { + attendingEvents: [{ // here we map the local relation to two public schema relations + spaceId: 'abc', + relationId: 'asd' + }, { + spaceId: 'abc', + relationId: 'qwe' + }], + authoredEvents: [{ + spaceId: 'abc', + relationId: 'qwe' + }] + } + } +} +``` + +While I think this design is feasible from a technical point of view, I'm concerned about the usability of the mappings file. + +## APIs + +### Create entity + +```ts +const entity = await create('User', { + name: 'John Doe', + age: 30, +}); +``` + +Create entity user with a new event + +```ts +const entity = await create('User', { + name: 'John Doe', + age: 30, + ownedEvents: [{ + name: 'Event 1', + }], +}); +``` + +Create entity and connect it to an existing event + +```ts +const entity = await create('User', { + name: 'John Doe', + age: 30, + ownedEvents: ['abc'], +}); +``` + +Mixing is allowed + +```ts +const entity = await create('User', { + name: 'John Doe', + age: 30, + ownedEvents: [ + 'abc', // connect to existing event + { name: 'Event 1' } // create new event + ], +}); +``` + +### Query entities + +We agreed that we explicitly query for the fields we want to get. Not sure if this is necessary. For relations I believe it's a good idea otherwise we don't know how many levels of relations we want to get and a simple query could become quite expensive. + +```ts +/* +[ + { name: 'John Doe', age: 30 }, + { name: 'Jane Doe', age: 25 } +] +*/ +const entities = await query({ + type: 'User', + fields: ['name', 'age'], +}); +``` + +Query with relations +```ts +/* +[ + { name: 'John Doe', age: 30, ownedEvents: [{ name: 'Event 1' }, { name: 'Event 2' }] }, + { name: 'Jane Doe', age: 25, ownedEvents: [{ name: 'Event 3' }] } +] +*/ +const entities = await query({ + type: 'User', + fields: ['name', 'age'], + relations: { + ownedEvents: { + type: 'Event', + fields: ['name'], + } + } +}); +``` + +Filters: + +```ts +const entities = await query({ + type: 'User', + fields: ['name', 'age'], + filters: { + age: { + '>': 20, + } + } +}); +``` + +## CLI command + +- Always ask for confirmation when ever applying changes showing a diff of the changes. +- This step can be skipped with a CLI flag that to skip the confirmation. Important for CI/CD pipelines. + +## CRDT Document Structure Thoughts + +In an automerge document I would store a list of entities based on their id. We don't want the ID to be included in the entry to avoid duplication as well as them getting out of sync. When querying we can inject the ID into the query results. + +This ensures that concurrent changes will apply per entity. Adding and removing entities also will merge well. + +Whenever an entity is remove, also all relations to that entity should be removed in the same Automerge change. In case a client does inject relations that are pointing to a removed entity clients can simply ignore them. + +While entities and relations could be stored in the same structure, it's probably better to separate them in order to construct query results, find relations and validate them. From, to and index are required for a relation to be valid. + +```ts +const automergeDocument = { + entities: { + '123': { name: 'John Doe', age: 30 }, + '234': { name: 'Jane Doe', age: 25, gender: 'female' }, + }, + relations: { + '987': { from: '123', to: '234', index: 'abc' }, + // relation with additional isAdmin field + '876': { from: '234', to: '123', index: 'def', isAdmin: true }, + } +} +``` \ No newline at end of file From 76f9ef7b99097b5ba0b6fa55ecd57807405456b9 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Mon, 9 Dec 2024 22:03:23 +0100 Subject: [PATCH 2/5] added clarifications & context --- docs/schema.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/schema.md b/docs/schema.md index b6ae6a8f..2dbd54c7 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -40,14 +40,14 @@ This section describes the different use cases for schema development including ## Rules - Each entity can have multiple types. -- All fields can be undefined +- Custom fields can be undefined. - A relation is only valid of a from, to and index exists. - All relations are many to many. They can have a hint with the `one max` field about the cardinality. - Relation can have additional fields. +- System properties e.g. `Created by` can't be undefined. ## Open Questions -- Can system properties also be undefined e.g. `Created by`? What to do in such a case? - Does every mapping include the space ID as well or only the type ID, attribute ID and relation ID? This depends of we want sync entities and relations from only one or multiple spaces. - Can a local schema type/field map to multiple public schema types/fields? e.g. local `User` maps to the public schema type `User` and `Person`. Therefor the email field should be mapped to the attribute `email` of the `User` type and the attribute `email` of the `Person` type which both might have the same ID, but can be two different fields as well. - What to do in case there are conflicting attributes e.g. `Location` and `Sensor` both have an `temperature` field and one is a number and the other a string. Do we merge them or how to map them to different fields? The most reasonable solution I can think of is to map them manually in the mappings file. @@ -58,7 +58,7 @@ This section describes the different use cases for schema development including - We want immediate feedback on invalid relations. - Handling invalid Relations - In case the from or to is missing we ignore the relation completely. - - In case the index is missing, we set an index at the end of the list. Later we can provide a callback to choose different behavior. + - In case the index is missing, we set an index at the end of the list. Later we can provide a callback to choose different behavior. Note: he data service should be validating this already, but can happen in case of end-to-end encrypted sync. - In case the index is not unique we pick one of them and move it between this and the next item. ### Design A @@ -192,7 +192,7 @@ class Organization { ## Mappings -- Mappings are optional. +- Mappings are optional. Context: So developers can start with a local schema and only create the mappings when they feel confident about the schema and publish it. - When syncing from public schema we must match the type ID, attribute ID and relation ID. ### Design 1 From 1ed9a3dc5184c7bb8cb8123eac4b09adedba260a Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Mon, 16 Dec 2024 16:33:11 +0100 Subject: [PATCH 3/5] add schema v2 draft --- docs/schema-v2.md | 270 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 docs/schema-v2.md diff --git a/docs/schema-v2.md b/docs/schema-v2.md new file mode 100644 index 00000000..44cfc601 --- /dev/null +++ b/docs/schema-v2.md @@ -0,0 +1,270 @@ +## Glossary + +- **Space**: A place for grouping information. +- **Private Space**: Information of a space that is not publicly accessible, but only accessible to members of a space. +- **Public Space**: Information of a space that is publicly accessible. +- **Personal Space**: A space controlled by a single person. + +## Relationship between Schema and Code + +The general idea is that the type definitions (schema) are also data. + +When building an app though you need to define the types in code. Therefor these are requirements we need to define: + +- Local schema as defined in the code allows the dev to full customize names & structure of the schema. +- Maximum interoperability with the Graph +- Every type and attribute has a corresponding entity that has been published to the public schema. When you define a schema in the code, it doesn't mean it exists in a public schema. + +## Spaces and Ownership + +While traditional apps are silos with the Graph, the users and organizations should be the owners of their data. + +When using an app you still should publish to your personal or public spaces that you control. +There can be app specific spaces, but the expected use-case is that these are use to define types in the graph. + +For the public schemas there is a global index and we need to specify to an indexer what spaces to index for a specific app. It's not yet clear or well defined how this works. + +## Schema Design + +## Schema Design + +- Relations are defined directly on the entity type (see an alternative Design below) +- We want immediate feedback on invalid relations. +- Handling invalid Relations + - In case the from or to is missing we ignore the relation completely. + - In case the index is missing, we set an index at the end of the list. Later we can provide a callback to choose different behavior. Note: he data service should be validating this already, but can happen in case of end-to-end encrypted sync. + - In case the index is not unique we pick one of them and move it between this and the next item. + +### Design A + +An object based schema definition where relations must be defined on the entity type. This design assumes that the local schema doesn't deal with entities having multiple types. This fact is only dealt with in the mappings file and therefor the local schema stays a lot simpler. + +```ts +export const schema = { + User: { + name: type.Text, + email: type.Text, + ownedEvents: type.Relation({ + types: 'Event', + }), + }, + Event: { + name: type.Text, + owners: type.Relation({ + types: 'User', + }), + }, +}; +``` + +Relations can be named to allow for multiple relations to the same type. + +```ts +export const schema = { + User: { + name: type.Text, + email: type.Text, + ownedEvents: type.Relation({ + map: 'user_owned_events', + types: 'Event', + }), + participatingEvents: type.Relation({ + map: 'event_participants', + types: 'Event', + }), + }, + Event: { + name: type.Text, + owners: type.Relation({ + map: 'user_owned_events', + types: 'User', + }), + participants: type.Relation({ + map: 'event_participants', + types: 'User', + }), + }, +}; +``` + +## Mapping + +The mapping should be simple and without unnecessary nesting. + +```ts +type AttributeId = string; + +type TypeMapping = { + id: string; // Public type ID + spaceId: string; // Public space ID (optional) + attributes: { + [localAttributeName: string]: AttributeId; + } +} + +type Mappings = [localTypeName: string]: TypeMapping +``` + +Example: +```ts +const mappings = { + User: { // matches the local type name + id: 'xyz', // matches the public type ID + spaceId: 'abc', // matches the public space ID + attributes: { + name: 'gfd', + email: 'asd', + } + }, + Event: { + typeID: 'wzx', + attributes: { + name: 'asd', + }, + }, +}; +``` + +This allows for simpler reasoning, but leaves room for edge-cases that lead to impossible types e.g. when creating an entity with attributes that don't match. Such edge-cases should by not allowing them and they should result in an Error. + +## Syncing Types (Local <-> Public) + +Use cases: + +1. Use an existing schema (use types from any different spaces) +2. Use an existing schema (use types from any different spaces) and extend it with new attributes +3. Use an existing schema (use types from any different spaces), but change types of attributes +4. Don't use any existing schema + +Publishing also must consider if the proposal for a schema change is rejected. + +-> To be defined + +## API + +We expect that in an app you mostly going to interact with the entities from one space. There we want to provide an API to set a default space ID for querying and creating entities. + +```ts +import { setDefaultSpaceId, subscribeToSpace } from '@graph-protocol/graph-framework'; + +setDefaultSpaceId('abc'); // this will automatically subscribe to the space + +// in order to manually subscribe to other spaces as well we can use the subscribeToSpace function +// this is important to get updates for the entities +subscribeToSpace('cde'); +``` + +When using React we can leverage a provider to provide the necessary information to the app. + +```tsx +import { GraphProvider } from '@graph-protocol/graph-framework'; + + + + +``` + +Note: if a function uses an spaceId that is not set as the default space ID or one of the spaces then it will throw an error. + +### Create a new entity + +Version 1: + +```ts +const item = createEntity( + ['User', 'Event'], + { + name: "John Doe", + }, + 'abc' // optional space ID where the entity should be published in +) +``` + +Version 2: + +```ts +const entity = createEntity({ + types: ['User', 'Event'], + attributes: { + name: "John Doe", + }, + spaceId: 'abc', // optional space ID where the entity should be published in +}) +``` + +#### Error cases + +In case you have two types with the same attribute names, but different types TypeScript should throw an error. + +e.g. + +```ts +export const schema = { + Sensor: { + temperature: type.Number, + }, + Location: { + temperature: type.Text, + }, +}; + +const entity = createEntity({ + types: ['Sensor', 'Location'], // throws an error since the attribute temperature is of different types + attributes: { + temperature: "John Doe", + }, +}) +``` + +### Publishing an entity + +This will automatically retrieve the latest public version of the entity, create a diff and based on that create an OPS to publish the difference. + +```ts +publishEdit(entity.id); +``` + +### Update an entity + +Update the attributes of an entity. TODO unclear if we can provide type-safety in this case. + +```ts +updateEntity({ + id: entity.id, + attributes: { + name: "Jane Doe", + }, +}); +``` + +Update the types and attributes of an entity. + +```ts +updateEntity({ + id: entity.id, + types: ['User', 'Sensor'], // optional to overwrite the types. This overwrites the entire list of types + attributes: { + name: "Jane Doe", + temperature: 123, + }, +}); +``` + +### Setting an attribute on an entity + +It's possible to set an attribute on an entity and publish it directly. Since there is no entry in the schema or mappings file we need to set and publish the attribute directly. If we only store the attribute locally on the next publish the attribute would not be taken into account since we don't know what's the public attribute ID. + +```ts +setAndPublishEntityAttribute({ + id: entity.id, + key: 'name', + value: 'John Doe', + attributeId: 'abc', +}); +``` + +### Querying entities + +Here we want to match the SDK for the public GraphQL. Still in progress here: https://www.notion.so/Data-block-query-strings-152273e214eb808898dac2d6b1b3820c + +TODO how do we distinguish between local and public queries? From 2456d23ae5b6186bb6344d139cc0b7f2315f9ce5 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Mon, 16 Dec 2024 21:37:45 +0100 Subject: [PATCH 4/5] improve draft v2 --- docs/schema-v2.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/schema-v2.md b/docs/schema-v2.md index 44cfc601..efd8d0bf 100644 --- a/docs/schema-v2.md +++ b/docs/schema-v2.md @@ -3,7 +3,7 @@ - **Space**: A place for grouping information. - **Private Space**: Information of a space that is not publicly accessible, but only accessible to members of a space. - **Public Space**: Information of a space that is publicly accessible. -- **Personal Space**: A space controlled by a single person. +- **Personal Space**: A space controlled by a single person or organization. ## Relationship between Schema and Code @@ -185,7 +185,7 @@ Version 2: ```ts const entity = createEntity({ types: ['User', 'Event'], - attributes: { + data: { name: "John Doe", }, spaceId: 'abc', // optional space ID where the entity should be published in @@ -221,7 +221,7 @@ const entity = createEntity({ This will automatically retrieve the latest public version of the entity, create a diff and based on that create an OPS to publish the difference. ```ts -publishEdit(entity.id); +publish(entity.id); ``` ### Update an entity From 0e098558caf07ae37b4637c83be795d9d96632e5 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Wed, 18 Dec 2024 16:48:49 +0100 Subject: [PATCH 5/5] add schema v2 improvements --- docs/schema-v2.md | 99 +++++++++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/docs/schema-v2.md b/docs/schema-v2.md index efd8d0bf..2f794a84 100644 --- a/docs/schema-v2.md +++ b/docs/schema-v2.md @@ -1,9 +1,33 @@ ## Glossary - **Space**: A place for grouping information. -- **Private Space**: Information of a space that is not publicly accessible, but only accessible to members of a space. -- **Public Space**: Information of a space that is publicly accessible. -- **Personal Space**: A space controlled by a single person or organization. +- **Private Space**: Information is only accessible to it's members and is directly governed by it's members. +- **Personal Space**: Information is publicly available and is directly governed by it's members. +- **Public Space**: Information is publicly available and is governed by the community. + +The table below should help to understand the relationship between the different space types. + + + + + + + + + + + + + + + + + + + + + +
Visibility
PrivatePublic
GovernancePrivatePrivatePersonal
Public(does not exist)Public
## Relationship between Schema and Code @@ -35,9 +59,9 @@ For the public schemas there is a global index and we need to specify to an inde - In case the index is missing, we set an index at the end of the list. Later we can provide a callback to choose different behavior. Note: he data service should be validating this already, but can happen in case of end-to-end encrypted sync. - In case the index is not unique we pick one of them and move it between this and the next item. -### Design A +### Design -An object based schema definition where relations must be defined on the entity type. This design assumes that the local schema doesn't deal with entities having multiple types. This fact is only dealt with in the mappings file and therefor the local schema stays a lot simpler. +An object based schema definition where relations must be defined on the entity type. ```ts export const schema = { @@ -65,22 +89,22 @@ export const schema = { name: type.Text, email: type.Text, ownedEvents: type.Relation({ - map: 'user_owned_events', + key: 'user_owned_events', types: 'Event', }), participatingEvents: type.Relation({ - map: 'event_participants', + key: 'event_participants', types: 'Event', }), }, Event: { name: type.Text, owners: type.Relation({ - map: 'user_owned_events', + key: 'user_owned_events', types: 'User', }), participants: type.Relation({ - map: 'event_participants', + key: 'event_participants', types: 'User', }), }, @@ -168,27 +192,13 @@ Note: if a function uses an spaceId that is not set as the default space ID or o ### Create a new entity -Version 1: - -```ts -const item = createEntity( - ['User', 'Event'], - { - name: "John Doe", - }, - 'abc' // optional space ID where the entity should be published in -) -``` - -Version 2: - ```ts const entity = createEntity({ types: ['User', 'Event'], data: { name: "John Doe", }, - spaceId: 'abc', // optional space ID where the entity should be published in + space: 'abc', // optional space ID where the entity should be published in }) ``` @@ -216,12 +226,33 @@ const entity = createEntity({ }) ``` -### Publishing an entity +### Publishing edits + +The idea behind it is that users will do several operations on various entities and then publish them all at once. We assume publishing operations per space is the most common use-case. That's why we want to provide a review process where the user can review the operations and remove parts that should not be published. + +The result is a multi-step process with 2 API calls: + +Step 1: `createEdit` calculates the `ops` based on the diff between the local and the latest public version, allows to add meta data e.g. name, additional authors +Step 2: user can review the `ops` and remove parts that should not be published +Step 3: `publish` submit the `edit` -This will automatically retrieve the latest public version of the entity, create a diff and based on that create an OPS to publish the difference. +In case the app knows the exact intentions manual review could be skipped or ops removed from the edit before presented to the user for review. ```ts -publish(entity.id); +const edit = createEdit({ + name: 'Add a new event', + authors: ["7UiGr3qnjZfRuKs3F3CX61", …] +}); + +const edit = createEdit({ + name: 'Add a new event', + authors: ["7UiGr3qnjZfRuKs3F3CX61", …], + space: 'abc', // optional spaceId to define the space where the edit should be published in +}); +``` + +```ts +publish(edit); ``` ### Update an entity @@ -250,21 +281,23 @@ updateEntity({ }); ``` -### Setting an attribute on an entity +### Setting an attribute/relation on an entity without a schema -It's possible to set an attribute on an entity and publish it directly. Since there is no entry in the schema or mappings file we need to set and publish the attribute directly. If we only store the attribute locally on the next publish the attribute would not be taken into account since we don't know what's the public attribute ID. +It's possible to set an attribute on an entity and publish it directly. Since there is no entry in the mappings file we use the attribute ID as key and set the value as value. This way with the next `createEdit` the attribute will be taken into account. ```ts -setAndPublishEntityAttribute({ - id: entity.id, - key: 'name', +setTriple({ + entity: entity.id, + attribute: 'abc', value: 'John Doe', - attributeId: 'abc', }); ``` +Adding/removing relations that are not defined in the schema still needs to be defined. + ### Querying entities Here we want to match the SDK for the public GraphQL. Still in progress here: https://www.notion.so/Data-block-query-strings-152273e214eb808898dac2d6b1b3820c TODO how do we distinguish between local and public queries? +