Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
aba0854
feat: start with cms event handlers
brunozoric Jan 26, 2026
ca7adb5
feat: cms model event handlers
brunozoric Jan 26, 2026
87d0c1c
refactor: change group before create to implement logger
brunozoric Jan 27, 2026
b01eb5b
refactor: implement logger
brunozoric Jan 27, 2026
9bd151b
fix
brunozoric Jan 27, 2026
4675610
wip: entry events
brunozoric Jan 27, 2026
cd822e4
wip: entry events
brunozoric Jan 27, 2026
4804251
wip: entry events
brunozoric Jan 27, 2026
97584e5
wip: entry event extended example
brunozoric Jan 27, 2026
319552a
wip: group use cases
brunozoric Jan 27, 2026
007d00c
wip: group use cases
brunozoric Jan 27, 2026
0069345
wip: model use cases
brunozoric Jan 28, 2026
118dffc
wip: model use cases
brunozoric Jan 28, 2026
59db1a3
wip: result
brunozoric Jan 29, 2026
91dbde2
wip: entry use cases
brunozoric Jan 29, 2026
79baa21
wip: entry use cases
brunozoric Jan 29, 2026
82bac72
wip: entry use cases
brunozoric Jan 29, 2026
e136b54
fix: result
brunozoric Jan 29, 2026
27170d2
fix: result
brunozoric Jan 29, 2026
9274a26
fix: entry extended
brunozoric Jan 29, 2026
c03c26a
fix: group
brunozoric Jan 29, 2026
c5765ac
fix: model
brunozoric Jan 29, 2026
e1f9195
fix: entry use case
brunozoric Jan 29, 2026
be76ebf
fix: group use case
brunozoric Jan 29, 2026
e4b8ddb
fix: model use case
brunozoric Jan 29, 2026
a0105c1
wip: builders
brunozoric Feb 2, 2026
cde5112
wip: group builder
brunozoric Feb 2, 2026
29deb02
wip: model builder
brunozoric Feb 2, 2026
eaf134c
fix: builders
brunozoric Feb 2, 2026
d114a6c
fix: graphql
brunozoric Feb 3, 2026
0101016
fix: graphql example
brunozoric Feb 3, 2026
1900eb5
fix: bg tasks
brunozoric Feb 3, 2026
09ac26f
fix: graphql
brunozoric Feb 3, 2026
db1d3f1
fix: bg tasks
brunozoric Feb 3, 2026
3cd2bee
fix: bg tasks
brunozoric Feb 3, 2026
89f755c
fix: bg tasks
brunozoric Feb 3, 2026
9f69166
fix: bg tasks
brunozoric Feb 4, 2026
eac182f
fix: bg tasks
brunozoric Feb 4, 2026
7a90624
fix: bg tasks
brunozoric Feb 4, 2026
86a4598
chore: format
brunozoric Feb 4, 2026
c708767
feat: mutation graphql example
brunozoric Feb 4, 2026
d7c8b71
chore: update ids
brunozoric Feb 4, 2026
5e3c187
chore: private model
brunozoric Feb 5, 2026
e0137e9
chore: single entry model
brunozoric Feb 5, 2026
7747a44
chore: private model update
brunozoric Feb 5, 2026
bf16efc
chore: private model update
brunozoric Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions docs/developer-docs/6.0.x/basic/result.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
id: bpki4nn3
title: Result
description: About Result
---

import {Alert} from "@/components/Alert";

<Alert type="success" title="WHAT YOU'LL LEARN">

- What is the `Result`?
- How to use it?
- How to use the return types?

</Alert>

## Overview

Webiny is utilizing the `Result` pattern, and there is a `Result` class that helps with that.

The `Result` pattern is a way to represent the outcome of an operation that can either succeed or fail.
Instead of throwing exceptions, functions return a `Result` object that encapsulates either a successful value or an error.

## Usage

Let's say you have a function that performs some async call to an operation which you import from a dependency, and it can either return a result or throw an error.

```typescript
import {Result} from "webiny/api";
import {someOperation} from "some-module";

const myMethod = async() => {
try {
const operationResult = await someOperation();
return Result.ok(operationResult);
} catch(ex) {
return Result.fail(ex);
}
}
const result = await myMethod();
if(result.isOk()) {
console.log("Operation succeeded with value:", result.value);
} else {
console.error("Operation failed with error:", result.error);
}
```

This way you can be sure that the method does not throw an exception, and you do not have to worry about it down the call stack, which uses the `myMethod` function.

## Return Types

When using the `Result` pattern the return types are inferred from the values passed to the `Result.ok()` and `Result.fail()` methods.

For example, if you have a function that returns a `Result` object with a successful value of type `string` and an error of type `Error`, the return type would be `Result<string, Error>`.

```typescript
import {Result} from "webiny/api";
const myMethod = async(): Promise<Result<string, Error>> => {
try {
const operationResult = await someOperation();
return Result.ok(operationResult);
} catch(ex) {
return Result.fail(ex);
}
}
```

For more complex return types, you can define custom types for the success value and error value.

```typescript
import type { Result } from "webiny/api";
import type { NotAuthorizedError, ValueNotFoundError } from "webiny/api/error";

export interface SuccessData {
id: string;
name: string;
}
export interface Success {
data: SuccessData;
timestamp: number;
}
interface PossibleErrors {
notFound: ValueNotFoundError;
notAuthorized: NotAuthorizedError;
}

type MyMethodErrors = PossibleErrors[keyof PossibleErrors];

type ReturnOfTheMethod = Promise<Result<Success, MyMethodErrors>>;

interface MyMethodParams {
id: string;
}

export interface MyMethod {
(params: MyMethodParams): ReturnOfTheMethod;
}
```

And then you can implement the method like this:

```typescript
import { Result } from "webiny/api";

const myMethod: MyMethod = async (params) => {
try {
const operationResult = await someOperation(params.id);
if (!operationResult) {
return Result.fail(new ValueNotFoundError(`Value with ID ${params.id} not found.`));
}
const successData: SuccessData = {
id: operationResult.id,
name: operationResult.name,
};
return Result.ok({
data: successData,
timestamp: Date.now(),
});
} catch (ex) {
return Result.fail(new NotAuthorizedError(`Not authorized to access value with ID ${params.id}.`));
}
};
```

## Result Unwrap

When using `Webiny` methods and functions that return a `Result` object, you might want to get the actual value or error types from the `Result` object.

If the type is not available for you to import, you can use the `Result.UnwrapResult` and `Result.UnwrapError` utility to get those types.

For example, using the `GetEntryByIdUseCase` from Headless CMS:

```typescript
import type { Result } from "webiny/api";
import type { GetEntryByIdUseCase } from "webiny/api/cms/entry";

export type IGetEntryByIdValue = Result.UnwrapResult<GetEntryByIdUseCase.Result>;
export type IGetEntryByIdError = Result.UnwrapError<GetEntryByIdUseCase.Result>;
```

This can be useful when you want to use return types from Webiny through your own code, but the types are not directly available for import.


## Conclusion
The `Result` pattern is a powerful way to handle success and failure in your code without relying on exceptions.
By using the `Result` class, you can create functions that return a `Result` object, making it easier to manage errors and success values in a consistent manner.
Additionally, the ability to infer return types from the `Result` object allows for better type safety and clarity in your code.
30 changes: 30 additions & 0 deletions docs/developer-docs/6.0.x/graphql/about.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
id: a6cd5pa6
title: About
description: About GraphQL
---

import {Alert} from "@/components/Alert";

<Alert type="success" title="WHAT YOU'LL LEARN">

- How Webiny uses GraphQL for its API layer?
- What is the GraphQL Schema Factory pattern?
- How to extend and customize GraphQL schemas?

</Alert>

## Overview

Webiny uses GraphQL as its primary API layer for all core applications including Headless CMS, Website Builder, and File Manager. GraphQL provides a flexible, type-safe way to query and mutate data, allowing clients to request exactly the data they need.

### GraphQL Schema Factory

Webiny implements a factory pattern for building GraphQL schemas. The `GraphQLSchemaFactory` interface allows you to extend and customize the GraphQL API by:

- **Adding type definitions** - Define your own GraphQL types, inputs, queries, and mutations
- **Registering resolvers** - Implement the logic that handles GraphQL operations
- **Dependency injection** - Access services and use cases within your resolvers
- **Modular architecture** - Build your schema in a composable, maintainable way

This approach keeps your GraphQL layer thin and focused on data access, while business logic lives in dedicated service classes. You can extend existing schemas or create entirely new GraphQL APIs that integrate seamlessly with Webiny's infrastructure.
204 changes: 204 additions & 0 deletions docs/developer-docs/6.0.x/graphql/example.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
---
id: ba8s58we
title: Example
description: GraphQL Example
---

import {Alert} from "@/components/Alert";

<Alert type="success" title="WHAT YOU'LL LEARN">

- How to add your own GraphQL schema?
- How to define GraphQL types, inputs, and mutations?
- How to add resolvers with dependency injection?
- How to handle errors in GraphQL responses?
- How to extend existing GraphQL types?

</Alert>

## Overview

Webiny provides a powerful GraphQL API framework that allows you to extend and customize the GraphQL schema. By implementing the `GraphQLSchemaFactory` interface, you can add custom types, queries, and mutations to your Webiny application.

The example demonstrates a complete implementation of a custom GraphQL query that lists CMS entries from any content model. It shows how to:

- Define GraphQL type definitions using `addTypeDefs()` with custom types and response structures
- Extend existing GraphQL types (like `Query`) with your custom operations
- Add resolvers using `addResolver()` with dependency injection support
- Handle both success and error responses using the Result pattern

This pattern follows Webiny architecture principles, keeping the GraphQL layer thin and delegating business logic to dedicated use cases. The resolver functions receive injected dependencies and handle the communication between the GraphQL API and your application services.


## Query Code Example

```typescript
import { GraphQLSchemaFactory } from "webiny/api/graphql";
import { GetModelUseCase } from "webiny/api/cms/model";
import { ListPublishedEntriesUseCase } from "webiny/api/cms/entry";

interface IListCmsEntriesArgs {
modelId: string;
limit?: number;
after?: string;
}

class ListCmsEntriesGraphQL implements GraphQLSchemaFactory.Interface {
public async execute(builder: GraphQLSchemaFactory.SchemaBuilder): GraphQLSchemaFactory.Return {
builder.addTypeDefs(/* GraphQL */ `
type CustomListCmsEntriesResponseItem {
id: ID!
title: String!
}
type CustomListCmsEntriesResponse {
data: [CustomListCmsEntriesResponseItem!]
meta: CmsListMeta
error: CmsError
}

extend type Query {
listCmsEntries(
modelId: ID!
limit: Int
after: String
): CustomListCmsEntriesResponse!
}
`);

builder.addResolver<IListCmsEntriesArgs>({
path: "Query.listCmsEntries",
dependencies: [GetModelUseCase, ListPublishedEntriesUseCase],
resolver(
getModel: GetModelUseCase.Interface,
listEntries: ListPublishedEntriesUseCase.Interface
) {
return async ({ args }) => {
const { modelId, limit, after } = args;

const modelResult = await getModel.execute(modelId);

if (modelResult.isFail()) {
return {
error: modelResult.error,
data: null,
meta: null
};
}
const model = modelResult.value;

const entriesResult = await listEntries.execute(model, {
limit: limit || 10,
after: after || null
});
if (entriesResult.isFail()) {
return {
error: entriesResult.error,
data: null,
meta: null
};
}

const { entries, meta } = entriesResult.value;

return {
data: entries.map(item => ({
id: item.id,
title: item.values[model.titleFieldId] || "No title"
})),
meta,
error: null
};
};
}
});

return builder;
}
}

export default GraphQLSchemaFactory.createImplementation({
implementation: ListCmsEntriesGraphQL,
dependencies: [],
});
```


## Mutation Code Example

```typescript
import { GraphQLSchemaFactory } from "webiny/api/graphql";
import { GetModelUseCase } from "webiny/api/cms/model";
import { CreateEntryUseCase } from "webiny/api/cms/entry";

interface ILogMyClickArgs {
id: string;
ip: string;
}

class LogMyClickGraphQL implements GraphQLSchemaFactory.Interface {
public async execute(builder: GraphQLSchemaFactory.SchemaBuilder): GraphQLSchemaFactory.Return {
builder.addTypeDefs(/* GraphQL */ `
type LogMyClickResponseItem {
id: ID!
ip: String!
createdOn: String!
}
type LogMyClickResponse {
data: LogMyClickResponseItem
error: CmsError
}

extend type Mutation {
logMyClick(id: ID!, ip: String!): LogMyClickResponse!
}
`);

builder.addResolver<ILogMyClickArgs>({
path: "Mutation.logMyClick",
dependencies: [GetModelUseCase, CreateEntryUseCase],
resolver(
getModel: GetModelUseCase.Interface,
createEntry: CreateEntryUseCase.Interface
) {
return async ({ args }) => {
const modelResult = await getModel.execute("logMyClickModel");

if (modelResult.isFail()) {
return {
error: modelResult.error,
data: null,
meta: null
};
}
const model = modelResult.value;

const result = await createEntry.execute<ILogMyClickArgs>(model, {
values: {
id: args.id,
ip: args.ip
}
});
if (result.isFail()) {
return {
error: result.error,
data: null,
meta: null
};
}
return {
data: result.value,
error: null
};
};
}
});

return builder;
}
}

export default GraphQLSchemaFactory.createImplementation({
implementation: LogMyClickGraphQL,
dependencies: [],
});
```
Loading