Skip to content

Commit

Permalink
Add support for set relation views (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulLeCam authored Feb 9, 2024
1 parent 2097b0c commit e82cb55
Show file tree
Hide file tree
Showing 28 changed files with 2,080 additions and 527 deletions.
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
"typescript": "^5.3.3"
},
"pnpm": {
"overrides": {}
"overrides": {
"@ceramicnetwork/3id-did-resolver": "^5.0.1-rc.0",
"@ceramicnetwork/cli": "^5.0.1-rc.0",
"@ceramicnetwork/common": "^5.0.1-rc.0",
"@ceramicnetwork/core": "^5.0.1-rc.0",
"@ceramicnetwork/http-client": "^5.0.1-rc.0",
"@ceramicnetwork/stream-model": "^4.0.1-rc.0",
"@ceramicnetwork/stream-model-instance": "^4.0.1-rc.0",
"@ceramicnetwork/streamid": "^5.0.0"
}
}
}
18 changes: 9 additions & 9 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@
]
},
"dependencies": {
"@ceramicnetwork/3id-did-resolver": "^4.1.0",
"@ceramicnetwork/cli": "^4.1.0",
"@ceramicnetwork/http-client": "^4.1.0",
"@ceramicnetwork/stream-model": "^3.1.0",
"@ceramicnetwork/stream-model-instance": "^3.1.0",
"@ceramicnetwork/streamid": "^4.1.0",
"@ceramicnetwork/3id-did-resolver": "^5.0.1-rc.0",
"@ceramicnetwork/cli": "^5.0.1-rc.0",
"@ceramicnetwork/http-client": "^5.0.1-rc.0",
"@ceramicnetwork/stream-model": "^4.0.1-rc.0",
"@ceramicnetwork/stream-model-instance": "^4.0.1-rc.0",
"@ceramicnetwork/streamid": "^5.0.0",
"@composedb/client": "workspace:^",
"@composedb/devtools": "workspace:^",
"@composedb/devtools-node": "workspace:^",
Expand All @@ -93,16 +93,16 @@
"listr-update-renderer": "^0.5.0",
"ora": "^8.0.1",
"terminal-size": "^4.0.0",
"uint8arrays": "^5.0.1"
"uint8arrays": "^5.0.2"
},
"devDependencies": {
"@ceramicnetwork/common": "^4.1.0",
"@ceramicnetwork/common": "^5.0.1-rc.0",
"@composedb/types": "workspace:^",
"@swc-node/register": "^1.8.0",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/listr": "^0.14.9",
"@types/node": "^20.11.16",
"@types/node": "^20.11.17",
"@types/update-notifier": "^6.0.8",
"ajv": "^8.12.0",
"execa": "^8.0.1",
Expand Down
8 changes: 4 additions & 4 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
"prepublishOnly": "package-check"
},
"dependencies": {
"@ceramicnetwork/http-client": "^4.1.0",
"@ceramicnetwork/stream-model": "^3.1.0",
"@ceramicnetwork/stream-model-instance": "^3.1.0",
"@ceramicnetwork/http-client": "^5.0.1-rc.0",
"@ceramicnetwork/stream-model": "^4.0.1-rc.0",
"@ceramicnetwork/stream-model-instance": "^4.0.1-rc.0",
"@composedb/constants": "workspace:^",
"@composedb/graphql-scalars": "workspace:^",
"@composedb/runtime": "workspace:^",
Expand All @@ -53,7 +53,7 @@
"graphql-relay": "^0.10.0"
},
"devDependencies": {
"@ceramicnetwork/common": "^4.1.0",
"@ceramicnetwork/common": "^5.0.1-rc.0",
"@composedb/devtools": "workspace:^",
"@composedb/test-schemas": "workspace:^",
"@composedb/types": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"prepublishOnly": "package-check"
},
"dependencies": {
"@ceramicnetwork/http-client": "^4.1.0",
"@ceramicnetwork/http-client": "^5.0.1-rc.0",
"@composedb/client": "workspace:^",
"@composedb/runtime": "workspace:^",
"@composedb/server": "workspace:^",
Expand Down
10 changes: 5 additions & 5 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
"prepublishOnly": "package-check"
},
"dependencies": {
"@ceramicnetwork/common": "^4.1.0",
"@ceramicnetwork/stream-model": "^3.1.0",
"@ceramicnetwork/streamid": "^4.1.0",
"@ceramicnetwork/common": "^5.0.1-rc.0",
"@ceramicnetwork/stream-model": "^4.0.1-rc.0",
"@ceramicnetwork/streamid": "^5.0.0",
"@composedb/graphql-scalars": "workspace:^",
"@didtools/cacao": "^3.0.1",
"@graphql-tools/schema": "^10.0.2",
Expand All @@ -52,14 +52,14 @@
"multiformats": "^13.0.1",
"object-hash": "^3.0.0",
"type-fest": "^4.10.2",
"uint8arrays": "^5.0.1"
"uint8arrays": "^5.0.2"
},
"devDependencies": {
"@composedb/test-schemas": "workspace:^",
"@composedb/types": "workspace:^",
"@types/jest": "^29.5.12",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.16",
"@types/node": "^20.11.17",
"@types/object-hash": "^3.0.6",
"ajv": "^8.12.0",
"dids": "^5.0.2",
Expand Down
18 changes: 16 additions & 2 deletions packages/devtools/src/formats/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { FieldsIndex } from '@ceramicnetwork/common'
import type {
ModelAccountRelationV2,
ModelDefinition,
ModelRelationsDefinitionV2,
ModelViewsDefinitionV2,
Expand Down Expand Up @@ -66,6 +67,7 @@ type RuntimeModelDefinition = {
export class RuntimeModelBuilder {
#accountData: Record<string, RuntimeViewReference> = {}
#commonEmbeds: Array<string>
#modelAccountRelation: ModelAccountRelationV2
#modelName: string
#modelRelations: ModelRelationsDefinitionV2
#modelSchema: JSONSchema.Object
Expand All @@ -77,6 +79,7 @@ export class RuntimeModelBuilder {

constructor(params: RuntimeModelBuilderParams) {
this.#commonEmbeds = params.commonEmbeds ?? []
this.#modelAccountRelation = params.definition.accountRelation
this.#modelName = params.name
this.#modelRelations = params.definition.relations ?? {}
this.#modelSchema = params.definition.schema
Expand Down Expand Up @@ -264,8 +267,19 @@ export class RuntimeModelBuilder {
_buildRelations(relations: ModelRelationsDefinitionV2 = {}): void {
for (const [key, relation] of Object.entries(relations)) {
if (relation.type === 'account') {
const relationKey = camelCase(`${key}Of${this.#modelName}List`)
this.#accountData[relationKey] = { type: 'account', name: this.#modelName, property: key }
const relationKey = camelCase(`${key}Of${this.#modelName}`)
this.#accountData[`${relationKey}List`] = {
type: 'account',
name: this.#modelName,
property: key,
}
if (this.#modelAccountRelation.type === 'set') {
this.#accountData[relationKey] = {
type: 'account-set',
name: this.#modelName,
property: key,
}
}
}
}
}
Expand Down
28 changes: 24 additions & 4 deletions packages/devtools/src/schema/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,16 @@ export class SchemaParser {
accountRelationFields?: Array<string>
description?: string
}
const accountRelation = args.accountRelation ?? 'LIST'
const accountRelationType = args.accountRelation ?? 'LIST'

const accountRelationValue = ACCOUNT_RELATIONS[isInterface ? 'NONE' : accountRelation]
if (accountRelationValue == null) {
const accountRelation = ACCOUNT_RELATIONS[isInterface ? 'NONE' : accountRelationType]
if (accountRelation == null) {
throw new Error(
`Unsupported accountRelation value ${accountRelation} for @createModel directive on object ${type.name}`,
`Unsupported accountRelation value ${accountRelationType} for @createModel directive on object ${type.name}`,
)
}

const accountRelationValue = { ...accountRelation }
if (accountRelationValue.type === 'set') {
const accountRelationFields = args.accountRelationFields
if (accountRelationFields == null) {
Expand Down Expand Up @@ -450,6 +451,25 @@ export class SchemaParser {
relation: { source: 'queryCount', model, property },
}
}
case 'relationSetFrom': {
if (!isObjectType(type)) {
throw new Error(
`Unsupported @relationSetFrom directive on field ${fieldName} of object ${objectName}, @relationSetFrom can only be set on a referenced object`,
)
}
const property = directive.args?.property as string | void
if (property == null) {
throw new Error(
`Missing property argument for @relationSetFrom directive on field ${fieldName} of object ${objectName}`,
)
}
return {
type: 'view',
required: false,
viewType: 'relation',
relation: { source: 'set', model: type.name, property },
}
}
}
}
}
Expand Down
56 changes: 35 additions & 21 deletions packages/devtools/src/schema/resolution.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { SignedCommitContainer } from '@ceramicnetwork/common'
import {
Model,
ModelViewDefinitionV2,
type ModelDefinition,
type ModelDefinitionV2,
type ModelViewDefinitionV2,
type ModelViewsDefinitionV2,
loadAllModelInterfaces,
} from '@ceramicnetwork/stream-model'
import type { StreamID } from '@ceramicnetwork/streamid'
import type { CeramicAPI, FieldsIndex } from '@composedb/types'

import { promiseMap } from '../utils.js'
import { isRelationViewDefinition, promiseMap } from '../utils.js'

import type {
AbstractCreateModelDefinition,
Expand Down Expand Up @@ -100,12 +100,7 @@ function executeCreateFactory(
const compositeViews: ModelViewsDefinitionV2 = {}
const viewsPromises: Record<string, Promise<ModelViewDefinitionV2>> = {}
for (const [name, view] of Object.entries(sourceDefinition.views ?? {})) {
if (
(view.type === 'relationCountFrom' ||
view.type === 'relationFrom' ||
view.type === 'relationDocument') &&
view.model !== null
) {
if (isRelationViewDefinition(view) && view.model !== null) {
const existing = executing[view.model]
if (existing == null) {
compositeViews[name] = view
Expand Down Expand Up @@ -198,6 +193,28 @@ function assertNoCircularDependency(
}
}

function assertValidSetRelationReference(
modelID: string,
refModelID: string,
refModel: ModelDefinition,
property: string,
) {
if (refModel.version === '1.0' || refModel.accountRelation.type !== 'set') {
throw new Error(`Invalid view referencing model ${refModelID}: expected "set" account relation`)
}
if (!refModel.accountRelation.fields.includes(property)) {
throw new Error(
`Invalid property ${property} set for view to model ${refModelID}: ${property} is not defined as a "set" account relation field`,
)
}
const relation = refModel.relations?.[property]
if (relation == null || relation.type !== 'document' || relation.model !== modelID) {
throw new Error(
`Invalid property ${property} set for view to model ${refModelID}: ${property} must define a relation to model ${modelID}`,
)
}
}

export async function createIntermediaryCompositeDefinition(
ceramic: CeramicAPI,
models: Record<string, AbstractModelDefinition>,
Expand Down Expand Up @@ -232,12 +249,7 @@ export async function createIntermediaryCompositeDefinition(
}

for (const view of Object.values(definition.model.views ?? {})) {
if (
(view.type === 'relationCountFrom' ||
view.type === 'relationFrom' ||
view.type === 'relationDocument') &&
view.model !== null
) {
if (isRelationViewDefinition(view) && view.model !== null) {
if (isInterface) {
// Views must be present in the model definition of interfaces
requiredDependencies.add(view.model)
Expand Down Expand Up @@ -320,18 +332,20 @@ export async function createIntermediaryCompositeDefinition(
}),
)
// Replace referenced models in composite views by their ID after all models are resolved
for (const modelViews of Object.values(definition.views)) {
for (const [modelID, modelViews] of Object.entries(definition.views)) {
for (const view of Object.values(modelViews)) {
if (
(view.type === 'relationCountFrom' ||
view.type === 'relationFrom' ||
view.type === 'relationDocument') &&
view.model !== null
) {
if (isRelationViewDefinition(view) && view.model !== null) {
const id = modelIDs[view.model]
if (id == null) {
throw new Error(`ID not found for referenced model ${view.model}`)
}
if (view.type === 'relationSetFrom') {
const refModel = definition.models[id]
if (refModel == null) {
throw new Error(`Model not found for ID ${id}`)
}
assertValidSetRelationReference(modelID, id, refModel, view.property)
}
view.model = id
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools/src/schema/type-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ directive @documentReference(model: String!) on FIELD_DEFINITION
# Relation views
directive @relationDocument(property: String!) on FIELD_DEFINITION
directive @relationFrom(model: String!, property: String!) on FIELD_DEFINITION
directive @relationFrom(model: String, property: String!) on FIELD_DEFINITION
directive @relationCountFrom(model: String!, property: String!) on FIELD_DEFINITION
directive @relationSetFrom(property: String!) on FIELD_DEFINITION
# Model definition
Expand Down
8 changes: 2 additions & 6 deletions packages/devtools/src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { InternalCompositeDefinition } from '@composedb/types'

import { createAbstractCompositeDefinition } from './schema/compiler.js'
import type { AbstractCompositeDefinition } from './schema/types.js'
import { isRelationViewDefinition } from './utils.js'

/** @internal */
export function mockDefinition(
Expand All @@ -20,12 +21,7 @@ export function mockDefinition(
definition.implements = definition.implements.map((name) => `${name}ID`)
}
for (const view of Object.values(definition.views ?? {})) {
if (
(view.type === 'relationCountFrom' ||
view.type === 'relationDocument' ||
view.type === 'relationFrom') &&
view.model !== null
) {
if (isRelationViewDefinition(view) && view.model !== null) {
view.model = `${view.model}ID`
}
}
Expand Down
Loading

0 comments on commit e82cb55

Please sign in to comment.