Skip to content

Commit 5a5786b

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth into feature/AdminForth/1647/do-not-create-a-prism-if-the-d
2 parents 5a5cc98 + dbffecb commit 5a5786b

55 files changed

Lines changed: 2815 additions & 2079 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

adminforth/dataConnectors/baseConnector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
4444
return this.client;
4545
}
4646

47-
setupClient(url: string): Promise<void> {
47+
setupClient(url: string, options?: { recovery?: boolean }): Promise<void> {
4848
throw new Error('Method not implemented.');
4949
}
5050

adminforth/dataConnectors/postgres.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,35 @@ types.setTypeParser(1082, (val) => val); // DATE
1616

1717
class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDataSourceConnector {
1818

19-
async setupClient(url: string): Promise<void> {
19+
async setupClient(url: string, options?: { recovery?: boolean }): Promise<void> {
2020
this.client = new Pool({
2121
connectionString: url
2222
});
23-
try {
24-
await this.client.connect();
25-
this.client.on('error', async (err) => {
26-
afLogger.error(`Postgres error: ${err.message} ${err.stack}`);
27-
this.client.end();
28-
await new Promise((resolve) => { setTimeout(resolve, 1000) });
29-
this.setupClient(url);
23+
24+
const selfHeal = options?.recovery !== false;
25+
26+
if (selfHeal) {
27+
this.client.on('error', (err) => {
28+
afLogger.error(`Postgres pool idle client error (pool self-heals on next query): ${err.message} ${err.stack}`);
3029
});
31-
} catch (e) {
32-
afLogger.error(`Failed to connect to Postgres ${e}`);
30+
try {
31+
const client = await this.client.connect();
32+
client.release();
33+
} catch (e) {
34+
afLogger.error(`Failed to connect to Postgres ${e}`);
35+
}
36+
} else {
37+
try {
38+
await this.client.connect();
39+
this.client.on('error', async (err) => {
40+
afLogger.error(`Postgres error: ${err.message} ${err.stack}`);
41+
this.client.end();
42+
await new Promise((resolve) => { setTimeout(resolve, 1000) });
43+
this.setupClient(url, options);
44+
});
45+
} catch (e) {
46+
afLogger.error(`Failed to connect to Postgres ${e}`);
47+
}
3348
}
3449
}
3550

adminforth/documentation/docs/tutorial/02-glossary.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ It used to:
1515

1616
There might be several datasources in the system for various databases e.g. One datasource to Mongo DBs and one to Postgres DB.
1717

18+
### connectionRecovery
19+
20+
For PostgreSQL datasources AdminForth keeps a connection pool. The optional `connectionRecovery` flag controls how the connector reacts when that connection drops (DB restart, failover, network blip, etc.):
21+
22+
```ts
23+
dataSources: [
24+
{
25+
id: 'maindb',
26+
url: `${process.env.DATABASE_URL}`,
27+
connectionRecovery: true, // default
28+
},
29+
],
30+
```
31+
32+
- `true` (default, recommended) — **self-heal mode.** The pool recovers automatically: a dead idle connection is dropped and a fresh one is transparently opened on the next query, so the app keeps working without a manual restart. Queries that were in-flight at the moment of the outage will fail, but subsequent queries succeed once the database is back.
33+
- `false`**legacy mode.** On a connection error the pool is destroyed and recreated after 1 second. If the outage outlasts that retry, the app can be left with a permanently dead pool and require a manual restart. Kept only for backward compatibility.
34+
35+
This flag is currently honored by the PostgreSQL connector; other connectors rely on their driver's built-in recovery.
36+
1837
## resource
1938

2039
A [Resource](/docs/api/Back/interfaces/AdminForthResource.md) is a AdminForth representation of a table or collection in database. One resource is one table in the database. Resource has `table` property which should be equal to the name of the table in the database.

adminforth/documentation/docs/tutorial/03-Customization/16-websocket.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ In two words, to subscribe to a topic from any frontend component you need to do
1313
```javascript
1414
import websocket from '@/websocket';
1515

16-
websocket.subscribe('/topic-name', (data) => {
16+
const unsubscribe = websocket.subscribe('/topic-name', (data) => {
1717
// this callback called when we receive publish in topic from the websocket
1818
console.log(data);
1919
});
20+
21+
// later, for example when the component is unmounted
22+
unsubscribe();
2023
```
2124

2225
On server you can publish data to the topic by calling
@@ -74,17 +77,17 @@ const props = defineProps({
7477
});
7578
7679
const totalCost: Ref<number|null> = ref(null);
80+
let unsubscribePropertyCost: (() => void) | undefined;
7781
7882
onMounted(() => {
79-
websocket.subscribe(`/property-cost/${props.adminUser!.pk}`, (data: any) => {
83+
unsubscribePropertyCost = websocket.subscribe(`/property-cost/${props.adminUser!.pk}`, (data: any) => {
8084
// this callback called when we receive publish in topic from the websocket
8185
totalCost.value = data.totalCost;
8286
});
8387
});
8488
8589
onUnmounted(() => {
86-
// will be called on logout
87-
websocket.unsubscribeAll();
90+
unsubscribePropertyCost?.();
8891
});
8992
9093
</script>
@@ -258,4 +261,4 @@ admin.websocket.publish(topic, { type: 'message', totalCost }, async (adminUser:
258261
259262
```
260263
261-
In this case during publish call it will check all users who subscribed to the topic and do actual publish to only those who are allowed to receive the message. This method requires more CPU resources and generally is not recommended.
264+
In this case during publish call it will check all users who subscribed to the topic and do actual publish to only those who are allowed to receive the message. This method requires more CPU resources and generally is not recommended.

adminforth/documentation/docs/tutorial/05-Adapters/05-ai-completion-adapters.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ new CompletionAdapterOpenAIResponses({
129129

130130
You can specify any GPT model you need. The default is `gpt-5-nano` as it is cheapest though may behave weakly.
131131

132-
By default, this adapter uses the OpenAI `responses` API (`v1/responses`) unless `useComplitionApi` is set to `true`. If `useComplitionApi` is `true`, it uses the older Chat Completions API (`v1/chat/completions`).
132+
By default, this adapter uses the OpenAI `responses` API (`v1/responses`) unless `useCompletionApi` is set to `true`. If `useCompletionApi` is `true`, it uses the older Chat Completions API (`v1/chat/completions`).
133133

134134

135135
### Using with OpenAI-compatible API providers (for example based on self-hosted vLLM docker images)
@@ -141,25 +141,25 @@ new CompletionAdapterOpenAIResponses({
141141
openAiApiKey: process.env.OVH_AI_ENDPOINTS_ACCESS_TOKEN as string,
142142
baseUrl: 'https://oai.endpoints.kepler.ai.cloud.ovh.net/v1',
143143
model: 'gpt-oss-20b',
144-
useComplitionApi: true,
144+
useCompletionApi: true,
145145
extraRequestBodyParameters: {
146146
store: false,
147147
},
148148
}),
149149
```
150150

151-
If `useComplitionApi` is omitted, the adapter keeps the current default behavior:
151+
If `useCompletionApi` is omitted, the adapter keeps the current default behavior:
152152

153153
- official OpenAI uses the `responses` API
154154
- custom `baseUrl` providers use the Chat Completions API.
155155

156-
This is because many OpenAI-compatible providers do not yet support the `responses` API or support it unstably (for example OVH AI Endpoints still - Apr 2026 does not fully support the `responses`, so `useComplitionApi: false` may work unstably there, though you can re-test it by manually enabling it by setting `useComplitionApi: true` and checking if it works).
156+
This is because many OpenAI-compatible providers do not yet support the `responses` API or support it unstably (for example OVH AI Endpoints still - Apr 2026 does not fully support the `responses`, so `useCompletionApi: false` may work unstably there, though you can re-test it by manually enabling it by setting `useCompletionApi: true` and checking if it works).
157157
Any 3rd-party API providers might have next reasones of pure `responses` API compatibility:
158158

159159
1) If they use vLLM open-source software under the hood they might have outdated version
160160
2) Custom non-vLLM implmentation might have reliable chat API implementation while give less priority to responses API as it is more complex and new.
161161

162-
We recommend you to try responses API first by setting `false` in `useComplitionApi` because it gives rich features set, including summarization and better structured outputs, and if it does not work for your provider, then set it to `true` to have less features but working implementation.
162+
We recommend you to try responses API first by setting `false` in `useCompletionApi` because it gives rich features set, including summarization and better structured outputs, and if it does not work for your provider, then set it to `true` to have less features but working implementation.
163163

164164

165165

0 commit comments

Comments
 (0)