Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ on:
push:
branches:
- main
- release-*
workflow_dispatch:

permissions:
Expand Down
1 change: 1 addition & 0 deletions docs/contribute/Ideas/v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Default services:
- CryptoService
- Registry
- EventService
- CacheService
- SessionManager
- Logger?

Expand Down
4 changes: 3 additions & 1 deletion docs/pages/Concepts/Context.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Context

The Context is used to expose Session and globally all information aroudn the current operation.
The Context is used to expose Session and globally all information around the current operation. It is the execution Context, it leverages NodeJS [Asynchronous context tracking](https://nodejs.org/api/async_context.html)

You can access the context from anywhere using the hook `useContext` from `@webda/core`.

As we have the ability to execute outside of `http` context the Context is not directly linked to the `http` request.

Expand Down
9 changes: 9 additions & 0 deletions docs/pages/Concepts/Models/Models.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ class MyModel extends CoreModel {
}
```

An action is defined as global if it is on a static method. You can also rename the action by providing a name.

```js title="src/mymodel.ts"
class MyModel extends CoreModel {
@Action({ name: "myAction" })
static globalAction() {}
}
```

## Model schemas

The schema is generated with [ts-json-schema-generator](https://github.com/vega/ts-json-schema-generator)
Expand Down
85 changes: 15 additions & 70 deletions docs/pages/Concepts/Services/Events.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,24 @@
# Events

The framework use the EventEmitter for events.
The framework does not use the EventEmitter for events since v4.

`emit()` this will not wait for any promise returned by listeners
We aligned on CloudEvents and its subscription system to allow easier integration with other systems.

`emitSync()` this will wait for resolution on all promises returned by listeners
To emit an event, just define the event as a class and use its `emit` method.
If you await the method then you will wait for its delivery and local listeners to be processed.

We also have a mechanism to listen asynchronously to events. They will then be posted to a Queue for them
to be consumed through a `AsyncEvent.worker()`
To listen to an event you can use the `on`.

```mermaid
sequenceDiagram
participant S as Service
participant As as AsyncEventService
participant Q as Queue
participant Aw as AsyncEventService Worker
As->>S: Bind event to a sendQueue listener
activate As
S->>As: Emit event
As->>Q: Push the event to the queue
deactivate As
Aw->>Q: Consume queue
Aw->>Aw: Call the original listener
```

### Webda.Init

### Webda.Init.Services

### Webda.Create.Services

### Webda.NewContext

### Webda.Request

### Store.Save

### Store.Saved

### Store.Update

### Store.Updated

### Store.PartialUpdate

### Store.PArtialUpdated

### Store.Delete
Subscription must be named from your service, this allows it to be overriden by configuration to execute asynchronously.

### Store.Deleted

### Store.Get

### Store.Find

### Store.Found

### Store.WebCreate

### Store.WebUpdate

### Store.WebGet

### Store.WebDelete

### Store.Action

### Store.Actionned

## Runtime Events

To implement some clients listeners we can allow listeners by uuid
```
emit -> @Emits()
on(name: string, event: string | Subscription , callback: async () => {})
```

addModelListener(model: string, uuid: string) // fullUuid?
removeModelListener(model: string, uuid: string) // fullUuid?
Local -> execute within current node process
PubSub -> execute on all nodes within the cluster
Queue -> execute once by some workers

Get the current map
The Pub/Sub will then send all events for this uuid.
EventService->worker(...subscription: string) -> if Queue subscription
EventService->init() -> will sub to all PubSub/Local on startup
124 changes: 19 additions & 105 deletions docs/pages/Concepts/Stores/Stores.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,129 +4,43 @@ sidebar_position: 4

# Stores

The store services allow you to store object in a NoSQL database it handles for you mapping between objects. Objects are mapped to a model to allow security policy and schema verification.
The store services allow you to store object in a database it handles for you mapping between objects. Objects are mapped to a model to allow security policy and schema verification.

We have currently File, DynamoDB and MongoDB storage
Available stores are:

:::warning

If you run several instances of your application, you should have a pub/sub to notify the other instances of the changes or disable cache by setting `disableCache` to `true`
- MemoryStore
- FileStore
- [DynamoDB](./DynamoDB.md)
- [MongoDB](./MongoDB.md)
- [PostgresStore](./PostgresStore.md)

:::
Stores are managing one or several models.

## Expose REST API
Example of a Store managing the model `MyModel`

Inside the configuration you can add a block for expose the store as a REST API
```mermaid

```js title="webda.config.json"
{
...
"expose": {
"url": "/storeurl", // By default the URL is the store name in lower case
"restrict": {
"update": true, // Prevent the creation of an object the PUT method wont be exposed
"delete": false // Allow delete for the object
}
}
...
}
```

The above configuration will end up creating the following routes:

- `POST /storeurl`
- `GET /storeurl/{uuid}`
- `DELETE /storeurl/{uuid}`

You can see that by default, once the store exposed all the methods are available unless you restrict them.

## Configuring Mapping

As an example we will use the Users / Idents stores used by the Authentication module.
The configuration would look like

A User has several Idents so in NoSQL we need to deduplicate a part of the Ident object inside an array inside the User object

The following is the Idents store configuration

```js title="webda.config.json"
```javascript title="webda.config.json"
{
...
"map": {
"Users": { // Target store
"key": "user", // Property inside Ident Object
"target": "idents", // Property on the User Object
"fields": "type", // Fields from the Ident Object ( uuid is added by default )
"cascade": true // If User object is delete then delete all the linked Idents
}
"stores": {
"mystore": {
"model": "mymodel",
}
}
```

So if you have a user like

```javascript
{
...
"uuid": "user_01"
}
```

Then you save a new Ident object like

```javascript
{
...
"uuid": "ident_01",
"user": "user_01",
"type": "Google"
}
```

Once the Ident saved, the User object will look like

```javascript
{
...
"uuid": "user_01",
"idents": [{"uuid":"ident_01","type":"Google"}]
...
}
```

Then if you update the field type on your Ident object the User object will reflect the change, as well as if you delete the ident object it will be removed from the User object.

If cascade = true, then if you delete the User object, all attached Idents will be delete aswell.

## Events

The Stores emit events to let you implement some auto completion of the object if needed or taking any others action even deny the action by throwing an exception

The store event looks like

```javascript
{
'object': object,
'store': this
}
```

Store.Save: Before saving the object
Store.Saved: After saving the object
Store.Update: Before updating the object
Store.Updated: After updating the object
Store.Delete: Before deleting the object
Store.Deleted: After deleting the object
Store.Get: When getting the object

### Owner Policy

POST: Add the current user in the user field of the object
PUT: Verify the current user is the user inside the user field
GET: Verify the current user is the user inside the user field, or a public=true field exists on the object
DELETE: Verify the current user is the user inside the user field
:::warning

### Void policy
If you run several instances of your application, you should have a pub/sub to notify the other instances of the changes or disable cache by setting `disableCache` to `true`

No verification, not recommended at all
:::

## Validation

Expand Down
5 changes: 5 additions & 0 deletions docs/pages/Deployments/Kubernetes/Kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@
You can deploy directly to Kubernetes.

The CronService is also compatible and translate the `@Cron` annotation into `CronJob` within Kubernetes.

## Updates

Check if resources are compatible with next version of Kubernetes.
https://github.com/kubepug/kubepug
26 changes: 26 additions & 0 deletions docs/pages/Protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,29 @@ sidebar_position: 2.091
## REST

## GraphQL

# Internal Protocols

StorageFinder supports a variety of protocols for different storage systems. The following is a list of supported protocols

`gs://` - Google Cloud Storage
`s3://` - Amazon S3
`http://` - HTTP
`https://` - HTTPS
`file://` - Local File System

PubSub service can be used to send and receive messages. The following is a list of supported protocols

`amqp://` - RabbitMQ
`sqs://` - Amazon SQS
`sns://` - Amazon SNS
`gcp-pubsub://` - Google Cloud PubSub

CryptoService manage encryption and decryption of data. The following is a list of supported protocols

`aes://` - AES
`rsa://` - RSA
`pgp://` - PGP
`jwt://` - JWT
`jwe://` - JWE
`jws://` - JWS
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"prettier-plugin-organize-imports": "^3.0.0",
"sinon": "^17.0.0",
"ts-node": "^10.1.0",
"typescript": "~5.3.2"
"typescript": "~5.4.5"
},
"author": "loopingz",
"license": "MIT",
Expand Down
12 changes: 12 additions & 0 deletions packages/async/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ You have different types of runner available.
It allows you to launch other language/binary from within your application
and capure the output.

## Scheduler

You can schedule an action to be executed at a specific time.

```typescript
this.getService<AsyncJobService>("AsyncJobService").scheduleJob(
AsyncAction.createAction("test", "echo", ["Hello World"]),
new Date(Date.now() + 1000)
);
```

<!-- README_FOOTER -->

## Sponsors

<!--
Expand Down
9 changes: 7 additions & 2 deletions packages/async/src/services/asyncjobservice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ class AsyncJobServiceTest extends WebdaTest {
};
await service.worker().cancel();
// @ts-ignore
service.runners = [];
assert.rejects(() => service.worker(), /AsyncJobService.worker requires runners/);
service.queue = {
// @ts-ignore
consume: callback => new CancelablePromise()
};
await service.worker().cancel();
service["runners"] = [];
await assert.rejects(() => service.worker(), /AsyncJobService.worker requires runners/);
}
/**
* Return a good initialized service
Expand Down
8 changes: 4 additions & 4 deletions packages/async/src/services/asyncjobservice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
CancelableLoopPromise,
CancelablePromise,
CloudBinary,
Constructor,
Core,
CoreModelDefinition,
CronDefinition,
Expand All @@ -18,9 +17,10 @@ import {
SimpleOperationContext,
Store,
WebContext,
WebdaError,
WebdaQL
WebdaError
} from "@webda/core";
import WebdaQL from "@webda/ql";
import { Constructor } from "@webda/tsc-esm";
import { WorkerLogLevel } from "@webda/workout";
import axios, { AxiosResponse } from "axios";
import * as crypto from "crypto";
Expand Down Expand Up @@ -697,7 +697,7 @@ export default class AsyncJobService<T extends AsyncJobServiceParameters = Async
// Map cron to an AsyncOperationAction
// It allows you to keep a trace of the cron execution in the AsyncAction
if (this.parameters.includeCron) {
CronService.loadAnnotations(this._webda.getServices()).forEach(cron => {
CronService.loadAnnotations(this.webda.getServices()).forEach(cron => {
this.log("INFO", `Schedule cron ${cron.cron}: ${cron.serviceName}.${cron.method}(...) # ${cron.description}`);
crontabSchedule(cron.cron, this.getCronExecutor(cron));
});
Expand Down
Loading