@aurabase/functions 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -9
- package/dist/index.cjs +45 -11
- package/dist/index.d.cts +64 -37
- package/dist/index.d.ts +64 -37
- package/dist/index.js +45 -11
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -23,12 +23,12 @@ Dans l'usage courant, `FunctionsService` n'est jamais instancié directement : i
|
|
|
23
23
|
```ts
|
|
24
24
|
import { createClient } from '@aurabase/aurabase-js'
|
|
25
25
|
|
|
26
|
-
const aura = createClient('
|
|
26
|
+
const aura = createClient('https://mon-projet.aurabase.cloud', 'aura_anon_xxx')
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### Invocation
|
|
30
30
|
|
|
31
|
-
`invoke<T>()`
|
|
31
|
+
`invoke<T>()` décrit la requête que votre fonction doit recevoir (`{ method, path, headers, body }`) et dé-wrap la réponse JSON du backend — tout est bufferisé, pas de streaming (voir Limitations).
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
const { data, error } = await aura.functions.invoke<{ ok: boolean }>('hello-world', {
|
|
@@ -36,10 +36,13 @@ const { data, error } = await aura.functions.invoke<{ ok: boolean }>('hello-worl
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
if (error) throw error
|
|
39
|
-
|
|
39
|
+
// `data` peut être `null` même en succès (204 / corps vide) : tester `data`
|
|
40
|
+
// plutôt que `error` évite l'assertion `!` et fait narrower TypeScript.
|
|
41
|
+
if (!data) throw new Error('réponse vide')
|
|
42
|
+
console.log(data.ok)
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
Options complètes : `method`, `path` (sous-route à l'intérieur de la fonction), `headers` (transmis à la fonction elle-même, pas des headers de transport), `signal`, `timeout`.
|
|
45
|
+
Options complètes : `body`, `method`, `path` (sous-route à l'intérieur de la fonction), `headers` (transmis à la fonction elle-même, pas des headers de transport), `signal`, `timeout`, `responseType`.
|
|
43
46
|
|
|
44
47
|
```ts
|
|
45
48
|
const controller = new AbortController()
|
|
@@ -58,11 +61,84 @@ if (error?.code === 'timeout') {
|
|
|
58
61
|
}
|
|
59
62
|
```
|
|
60
63
|
|
|
64
|
+
#### Ce que la fonction reçoit
|
|
65
|
+
|
|
66
|
+
`method`, `path`, `headers` et `body` décrivent la requête **vue par votre fonction** — pas le transport HTTP vers Aurabase :
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
await aura.functions.invoke('items', {
|
|
70
|
+
method: 'PUT',
|
|
71
|
+
path: '/items/1',
|
|
72
|
+
headers: { 'x-request-source': 'sdk' },
|
|
73
|
+
body: { qty: 2 },
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// items.ts (runtime Deno)
|
|
79
|
+
Deno.serve(async (req) => {
|
|
80
|
+
req.method // 'PUT'
|
|
81
|
+
new URL(req.url).pathname // '/items/1'
|
|
82
|
+
req.headers.get('x-request-source') // 'sdk'
|
|
83
|
+
await req.json() // { qty: 2 } ← le corps, PAS une enveloppe
|
|
84
|
+
return new Response('ok')
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Défauts si vous ne précisez rien : `POST` sur `/`. `GET` et `HEAD` sont envoyés sans corps (la spec Fetch l'interdit) ; un `body` non-chaîne est sérialisé en JSON et `content-type: application/json` est posé si vous n'en fixez pas un.
|
|
89
|
+
|
|
90
|
+
Sans `body`, la fonction reçoit une requête **sans corps** (parité `@supabase/functions-js`) : un `await req.json()` inconditionnel y échoue. Écrivez `await req.json().catch(() => ({}))` si l'appel peut être fait sans charge utile.
|
|
91
|
+
|
|
92
|
+
Les en-têtes de cadrage/transport (`content-length`, `transfer-encoding`, `connection`, `keep-alive`, `upgrade`, `host`…) passés dans `headers` sont ignorés : ils décrivent le message, pas la requête applicative, et un `content-length` forgé désynchroniserait la lecture du corps.
|
|
93
|
+
|
|
94
|
+
> **Changement de comportement (correctif C20).** Jusqu'à ce correctif, une fonction Deno recevait invariablement un `POST /invoke/{project_id}/{name}` dont le corps était l'enveloppe `{ method, path, headers, body }`. Une fonction écrite contre cet ancien comportement (`const { body } = await req.json()`) doit être ajustée : lisez désormais `await req.json()` directement. Les modèles de fonctions du Studio et les exemples de ce README étaient déjà écrits selon le contrat correct — ils ne fonctionnaient donc pas avant ce correctif.
|
|
95
|
+
|
|
96
|
+
#### Type de réponse (`responseType`)
|
|
97
|
+
|
|
98
|
+
Défaut `'json'` : le corps est parsé et dé-wrappé (`{data}` → contenu). Les autres valeurs renvoient le corps brut de la fonction, sans dé-wrapping, et `error.status` porte alors le **vrai** statut HTTP de la fonction :
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const { data: csv } = await aura.functions.invoke<string>('export', { responseType: 'text' })
|
|
102
|
+
const { data: png } = await aura.functions.invoke<Blob>('render', { responseType: 'blob' })
|
|
103
|
+
const { data: buf } = await aura.functions.invoke<ArrayBuffer>('pdf', { responseType: 'arrayBuffer' })
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Erreurs typées
|
|
107
|
+
|
|
108
|
+
Parité `@supabase/functions-js`. Les deux classes sous-classent `AuraError` (donc `isAuraError()`, `status`, `code` restent valides) :
|
|
109
|
+
|
|
110
|
+
| Classe | Quand | Statuts |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `FunctionsHttpError` | **votre fonction** a répondu en erreur | tout non-2xx hors relais (400, 404, 500…) |
|
|
113
|
+
| `FunctionsRelayError` | la réponse de votre fonction n'est jamais arrivée | 0 (réseau), 502, 503, 504 |
|
|
114
|
+
|
|
115
|
+
Deux statuts de relais à ne pas confondre — ils ne se rejouent pas de la même façon :
|
|
116
|
+
|
|
117
|
+
- **503 `SERVICE_UNAVAILABLE`** : l'edge-runtime est injoignable, votre fonction **n'a pas** été exécutée → réessayer est sûr ;
|
|
118
|
+
- **504 `TIMEOUT`** : votre fonction a dépassé son budget (`permissions.timeout_secs` + marge). Elle **a été lancée** et a pu produire ses effets de bord ; seule la réponse manque → ne rejouez pas une opération non idempotente à l'aveugle.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { FunctionsHttpError, FunctionsRelayError, isRelayStatus } from '@aurabase/functions'
|
|
122
|
+
|
|
123
|
+
const { error } = await aura.functions.invoke('checkout', { responseType: 'text' })
|
|
124
|
+
if (error instanceof FunctionsRelayError) {
|
|
125
|
+
// rien n'a été exécuté côté fonction → réessayer est sûr
|
|
126
|
+
} else if (error instanceof FunctionsHttpError) {
|
|
127
|
+
// la fonction a bien tourné et a répondu en erreur → déboguer VOTRE code
|
|
128
|
+
}
|
|
129
|
+
isRelayStatus(error?.status ?? 0) // même décision, sans instanceof
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Une annulation (`timeout`/`signal`) reste une `AuraError` de code `timeout`, jamais une de ces deux classes.
|
|
133
|
+
|
|
61
134
|
### Fonctions (métadonnées)
|
|
62
135
|
|
|
136
|
+
`get()` accepte le **nom** ou l'**identifiant** (B10) — en cas d'homonymie, le nom l'emporte :
|
|
137
|
+
|
|
63
138
|
```ts
|
|
64
139
|
const { data: fns } = await aura.functions.list()
|
|
65
|
-
const { data:
|
|
140
|
+
const { data: parId } = await aura.functions.get(fns![0]!.id)
|
|
141
|
+
const { data: parNom } = await aura.functions.get('hello-world')
|
|
66
142
|
```
|
|
67
143
|
|
|
68
144
|
### Jobs asynchrones (extra vs `@supabase/functions-js`)
|
|
@@ -85,11 +161,12 @@ await aura.functions.deleteCronJob('nightly-cleanup')
|
|
|
85
161
|
|
|
86
162
|
## Limitations
|
|
87
163
|
|
|
88
|
-
- Pas de streaming de réponse : `invoke()` bufferise toujours l'intégralité du corps
|
|
89
|
-
- Pas de `responseType` (`json`/`text`/`blob`) : la réponse est toujours parsée comme JSON puis dé-wrappée (`{data}` → contenu).
|
|
164
|
+
- Pas de streaming de réponse : `invoke()` bufferise toujours l'intégralité du corps avant de résoudre (pas de lecture incrémentale, pas de réponse SSE/chunked exploitable). `responseType` change le DÉCODAGE du corps bufferisé, pas ce point.
|
|
90
165
|
- Pas de sélection de région d'exécution.
|
|
91
|
-
- `headers` est transmis
|
|
92
|
-
-
|
|
166
|
+
- `headers` est transmis à la fonction invoquée — ce ne sont pas des headers de transport HTTP (pas de contrôle sur ceux injectés par le pipeline : `apikey`, `Authorization`). La fonction ne voit QUE les en-têtes que vous passez (plus le `content-type` par défaut) : les en-têtes internes du transport ne lui sont pas exposés.
|
|
167
|
+
- Le statut réel de la fonction n'est visible qu'avec un `responseType` autre que `'json'` : en mode `'json'` la réponse est enveloppée et le transport rend toujours HTTP 200 (le statut de la fonction est alors dans `data.status`).
|
|
168
|
+
- Pas de `getCronJob()` ni `updateCronJob()` : seuls `listCronJobs`, `createCronJob` et `deleteCronJob` sont exposés. Pas de `deploy()` / `delete()` de fonction depuis le SDK : le déploiement passe par l'API HTTP, la CLI `aura functions deploy` ou le Studio.
|
|
169
|
+
- `deleteCronJob(name)` et les routes `PATCH`/`DELETE`/`invoke` d'une fonction attendent un **nom** ; seul `get()` accepte aussi l'identifiant.
|
|
93
170
|
|
|
94
171
|
## Documentation
|
|
95
172
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@aurabase/core');
|
|
4
4
|
|
|
5
|
-
// src/
|
|
5
|
+
// src/errors.ts
|
|
6
6
|
var RELAY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
7
7
|
function isRelayStatus(status) {
|
|
8
8
|
return status === 0 || RELAY_STATUSES.has(status);
|
|
@@ -29,19 +29,36 @@ function toFunctionsError(err, context) {
|
|
|
29
29
|
const body = err.details != null ? err.details : { error: { code: err.code, message: err.message } };
|
|
30
30
|
return isRelayStatus(err.status) ? new FunctionsRelayError(err.status, body, err.details) : new FunctionsHttpError(err.status, body, err.details);
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
// src/FunctionsService.ts
|
|
34
32
|
var FunctionsService = class extends core.BaseService {
|
|
35
33
|
constructor(client) {
|
|
36
34
|
super(client);
|
|
37
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
38
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
39
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
40
|
+
*/
|
|
38
41
|
get prefix() {
|
|
39
|
-
return
|
|
42
|
+
return "/v1/functions";
|
|
40
43
|
}
|
|
41
44
|
// -- Function invocation ----------------------------------------------------
|
|
42
45
|
/**
|
|
43
46
|
* Invoque une edge function.
|
|
44
47
|
*
|
|
48
|
+
* Ce que la fonction REÇOIT (C20) : la requête vue par votre code est celle que vous
|
|
49
|
+
* décrivez ici — `method` (défaut `POST`), `path` (défaut `/`), `headers` et `body`
|
|
50
|
+
* APPLICATIF. Sur le runtime Deno :
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* // invoke('x', { method: 'PUT', path: '/items/1', headers: { 'x-src': 'sdk' }, body: { a: 1 } })
|
|
54
|
+
* Deno.serve(async (req) => {
|
|
55
|
+
* req.method // 'PUT'
|
|
56
|
+
* new URL(req.url).pathname // '/items/1'
|
|
57
|
+
* req.headers.get('x-src') // 'sdk'
|
|
58
|
+
* await req.json() // { a: 1 } ← le corps, PAS l'enveloppe
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
45
62
|
* `responseType` (parité `@supabase/functions-js`, défaut `'json'`) :
|
|
46
63
|
* - `'json'` : chemin PIPELINE inchangé (refresh proactif centralisé, injection
|
|
47
64
|
* apikey/Authorization, dé-wrapping `{data,meta}`, retryConfig). Non-régression.
|
|
@@ -52,8 +69,9 @@ var FunctionsService = class extends core.BaseService {
|
|
|
52
69
|
*
|
|
53
70
|
* Erreurs typées (parité) : une réponse HTTP non-2xx → `FunctionsHttpError`, une
|
|
54
71
|
* panne de relais/infra (réseau, 502/503/504) → `FunctionsRelayError` ; les deux
|
|
55
|
-
* sous-classent `AuraError
|
|
56
|
-
* `
|
|
72
|
+
* sous-classent `AuraError`, ce qui est l'invariant du typage d'erreur du SDK :
|
|
73
|
+
* `isAuraError` reste vrai et le champ `error` reste lisible sur les deux. `timeout`/
|
|
74
|
+
* `signal` produisent le code `timeout`.
|
|
57
75
|
*/
|
|
58
76
|
async invoke(slug, options) {
|
|
59
77
|
const controller = new AbortController();
|
|
@@ -69,7 +87,12 @@ var FunctionsService = class extends core.BaseService {
|
|
|
69
87
|
if (timeoutId) clearTimeout(timeoutId);
|
|
70
88
|
if (userSignal) userSignal.removeEventListener("abort", onAbort);
|
|
71
89
|
};
|
|
72
|
-
const envelope = {
|
|
90
|
+
const envelope = {
|
|
91
|
+
method: options?.method,
|
|
92
|
+
path: options?.path,
|
|
93
|
+
headers: options?.headers,
|
|
94
|
+
body: options?.body
|
|
95
|
+
};
|
|
73
96
|
const responseType = options?.responseType ?? "json";
|
|
74
97
|
const timeoutError = () => ({
|
|
75
98
|
data: null,
|
|
@@ -79,8 +102,9 @@ var FunctionsService = class extends core.BaseService {
|
|
|
79
102
|
});
|
|
80
103
|
try {
|
|
81
104
|
if (responseType === "json") {
|
|
105
|
+
const hasCanceller = options?.timeout !== void 0 || userSignal !== void 0;
|
|
82
106
|
const res2 = await this.request("POST", `${this.prefix}/${slug}/invoke`, envelope, {
|
|
83
|
-
signal: controller.signal
|
|
107
|
+
...hasCanceller ? { signal: controller.signal } : {}
|
|
84
108
|
});
|
|
85
109
|
if (res2.error && controller.signal.aborted) return timeoutError();
|
|
86
110
|
if (res2.error) return { data: null, error: toFunctionsError(res2.error) };
|
|
@@ -125,7 +149,10 @@ var FunctionsService = class extends core.BaseService {
|
|
|
125
149
|
return {
|
|
126
150
|
data: null,
|
|
127
151
|
error: new FunctionsRelayError(0, {
|
|
128
|
-
error: {
|
|
152
|
+
error: {
|
|
153
|
+
code: "network_error",
|
|
154
|
+
message: err instanceof Error ? err.message : "Function relay error"
|
|
155
|
+
}
|
|
129
156
|
})
|
|
130
157
|
};
|
|
131
158
|
} finally {
|
|
@@ -136,8 +163,15 @@ var FunctionsService = class extends core.BaseService {
|
|
|
136
163
|
async list() {
|
|
137
164
|
return this.request("GET", `${this.prefix}`);
|
|
138
165
|
}
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Lit une fonction par son **nom** ou par son **identifiant** (B10).
|
|
168
|
+
*
|
|
169
|
+
* Les deux clés sont acceptées : `list()` renvoie `{ id, name, … }` et les deux sont des
|
|
170
|
+
* points d'entrée naturels. En cas d'homonymie (une fonction PEUT s'appeler comme un UUID),
|
|
171
|
+
* le NOM l'emporte. Une valeur qui n'est ni l'un ni l'autre produit un 404 qui le dit.
|
|
172
|
+
*/
|
|
173
|
+
async get(nameOrId) {
|
|
174
|
+
return this.request("GET", `${this.prefix}/${nameOrId}`);
|
|
141
175
|
}
|
|
142
176
|
// -- Jobs -------------------------------------------------------------------
|
|
143
177
|
async listJobs(options) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,14 +1,66 @@
|
|
|
1
|
-
import { BaseService, AuraClientLike, AuraResponse, FunctionDef, Job, CronJob
|
|
1
|
+
import { AuraError, BaseService, AuraClientLike, AuraResponse, FunctionDef, Job, CronJob } from '@aurabase/core';
|
|
2
2
|
export { AuraResponse, CronJob, FunctionDef, Job } from '@aurabase/core';
|
|
3
3
|
|
|
4
|
+
declare function isRelayStatus(status: number): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Erreur HTTP APPLICATIVE renvoyée par l'invocation d'une edge function
|
|
7
|
+
* (réponse HTTP non-2xx). Parité `FunctionsHttpError` de `@supabase/functions-js`.
|
|
8
|
+
* Sous-classe `AuraError` → `isAuraError()` reste vrai, `status` porte le code HTTP.
|
|
9
|
+
* `context` conserve le corps déjà lu (JSON/texte) pour inspection.
|
|
10
|
+
*/
|
|
11
|
+
declare class FunctionsHttpError extends AuraError {
|
|
12
|
+
readonly context: unknown;
|
|
13
|
+
constructor(status: number, body?: {
|
|
14
|
+
error?: {
|
|
15
|
+
code?: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
};
|
|
18
|
+
message?: string;
|
|
19
|
+
} | null, context?: unknown);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Erreur de RELAIS/infra : fonction injoignable (échec réseau), 502/503/504 de la
|
|
23
|
+
* passerelle, cold start échoué, corps non exploitable. Parité `FunctionsRelayError`
|
|
24
|
+
* de `@supabase/functions-js`. Sous-classe `AuraError`.
|
|
25
|
+
*/
|
|
26
|
+
declare class FunctionsRelayError extends AuraError {
|
|
27
|
+
readonly context: unknown;
|
|
28
|
+
constructor(status: number, body?: {
|
|
29
|
+
error?: {
|
|
30
|
+
code?: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
};
|
|
33
|
+
message?: string;
|
|
34
|
+
} | null, context?: unknown);
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
/** Type de réponse attendu de l'invocation (parité `@supabase/functions-js`). */
|
|
5
38
|
type FunctionsResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer';
|
|
6
39
|
declare class FunctionsService extends BaseService {
|
|
7
40
|
constructor(client: AuraClientLike);
|
|
41
|
+
/**
|
|
42
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
43
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
44
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
45
|
+
*/
|
|
8
46
|
private get prefix();
|
|
9
47
|
/**
|
|
10
48
|
* Invoque une edge function.
|
|
11
49
|
*
|
|
50
|
+
* Ce que la fonction REÇOIT (C20) : la requête vue par votre code est celle que vous
|
|
51
|
+
* décrivez ici — `method` (défaut `POST`), `path` (défaut `/`), `headers` et `body`
|
|
52
|
+
* APPLICATIF. Sur le runtime Deno :
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* // invoke('x', { method: 'PUT', path: '/items/1', headers: { 'x-src': 'sdk' }, body: { a: 1 } })
|
|
56
|
+
* Deno.serve(async (req) => {
|
|
57
|
+
* req.method // 'PUT'
|
|
58
|
+
* new URL(req.url).pathname // '/items/1'
|
|
59
|
+
* req.headers.get('x-src') // 'sdk'
|
|
60
|
+
* await req.json() // { a: 1 } ← le corps, PAS l'enveloppe
|
|
61
|
+
* })
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
12
64
|
* `responseType` (parité `@supabase/functions-js`, défaut `'json'`) :
|
|
13
65
|
* - `'json'` : chemin PIPELINE inchangé (refresh proactif centralisé, injection
|
|
14
66
|
* apikey/Authorization, dé-wrapping `{data,meta}`, retryConfig). Non-régression.
|
|
@@ -19,8 +71,9 @@ declare class FunctionsService extends BaseService {
|
|
|
19
71
|
*
|
|
20
72
|
* Erreurs typées (parité) : une réponse HTTP non-2xx → `FunctionsHttpError`, une
|
|
21
73
|
* panne de relais/infra (réseau, 502/503/504) → `FunctionsRelayError` ; les deux
|
|
22
|
-
* sous-classent `AuraError
|
|
23
|
-
* `
|
|
74
|
+
* sous-classent `AuraError`, ce qui est l'invariant du typage d'erreur du SDK :
|
|
75
|
+
* `isAuraError` reste vrai et le champ `error` reste lisible sur les deux. `timeout`/
|
|
76
|
+
* `signal` produisent le code `timeout`.
|
|
24
77
|
*/
|
|
25
78
|
invoke<T = unknown>(slug: string, options?: {
|
|
26
79
|
body?: unknown;
|
|
@@ -32,7 +85,14 @@ declare class FunctionsService extends BaseService {
|
|
|
32
85
|
responseType?: FunctionsResponseType;
|
|
33
86
|
}): Promise<AuraResponse<T>>;
|
|
34
87
|
list(): Promise<AuraResponse<FunctionDef[]>>;
|
|
35
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Lit une fonction par son **nom** ou par son **identifiant** (B10).
|
|
90
|
+
*
|
|
91
|
+
* Les deux clés sont acceptées : `list()` renvoie `{ id, name, … }` et les deux sont des
|
|
92
|
+
* points d'entrée naturels. En cas d'homonymie (une fonction PEUT s'appeler comme un UUID),
|
|
93
|
+
* le NOM l'emporte. Une valeur qui n'est ni l'un ni l'autre produit un 404 qui le dit.
|
|
94
|
+
*/
|
|
95
|
+
get(nameOrId: string): Promise<AuraResponse<FunctionDef>>;
|
|
36
96
|
listJobs(options?: {
|
|
37
97
|
status?: string;
|
|
38
98
|
limit?: number;
|
|
@@ -45,37 +105,4 @@ declare class FunctionsService extends BaseService {
|
|
|
45
105
|
deleteCronJob(name: string): Promise<AuraResponse<null>>;
|
|
46
106
|
}
|
|
47
107
|
|
|
48
|
-
declare function isRelayStatus(status: number): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Erreur HTTP APPLICATIVE renvoyée par l'invocation d'une edge function
|
|
51
|
-
* (réponse HTTP non-2xx). Parité `FunctionsHttpError` de `@supabase/functions-js`.
|
|
52
|
-
* Sous-classe `AuraError` → `isAuraError()` reste vrai, `status` porte le code HTTP.
|
|
53
|
-
* `context` conserve le corps déjà lu (JSON/texte) pour inspection.
|
|
54
|
-
*/
|
|
55
|
-
declare class FunctionsHttpError extends AuraError {
|
|
56
|
-
readonly context: unknown;
|
|
57
|
-
constructor(status: number, body?: {
|
|
58
|
-
error?: {
|
|
59
|
-
code?: string;
|
|
60
|
-
message?: string;
|
|
61
|
-
};
|
|
62
|
-
message?: string;
|
|
63
|
-
} | null, context?: unknown);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Erreur de RELAIS/infra : fonction injoignable (échec réseau), 502/503/504 de la
|
|
67
|
-
* passerelle, cold start échoué, corps non exploitable. Parité `FunctionsRelayError`
|
|
68
|
-
* de `@supabase/functions-js`. Sous-classe `AuraError`.
|
|
69
|
-
*/
|
|
70
|
-
declare class FunctionsRelayError extends AuraError {
|
|
71
|
-
readonly context: unknown;
|
|
72
|
-
constructor(status: number, body?: {
|
|
73
|
-
error?: {
|
|
74
|
-
code?: string;
|
|
75
|
-
message?: string;
|
|
76
|
-
};
|
|
77
|
-
message?: string;
|
|
78
|
-
} | null, context?: unknown);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
108
|
export { FunctionsHttpError, FunctionsRelayError, type FunctionsResponseType, FunctionsService, isRelayStatus };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,66 @@
|
|
|
1
|
-
import { BaseService, AuraClientLike, AuraResponse, FunctionDef, Job, CronJob
|
|
1
|
+
import { AuraError, BaseService, AuraClientLike, AuraResponse, FunctionDef, Job, CronJob } from '@aurabase/core';
|
|
2
2
|
export { AuraResponse, CronJob, FunctionDef, Job } from '@aurabase/core';
|
|
3
3
|
|
|
4
|
+
declare function isRelayStatus(status: number): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Erreur HTTP APPLICATIVE renvoyée par l'invocation d'une edge function
|
|
7
|
+
* (réponse HTTP non-2xx). Parité `FunctionsHttpError` de `@supabase/functions-js`.
|
|
8
|
+
* Sous-classe `AuraError` → `isAuraError()` reste vrai, `status` porte le code HTTP.
|
|
9
|
+
* `context` conserve le corps déjà lu (JSON/texte) pour inspection.
|
|
10
|
+
*/
|
|
11
|
+
declare class FunctionsHttpError extends AuraError {
|
|
12
|
+
readonly context: unknown;
|
|
13
|
+
constructor(status: number, body?: {
|
|
14
|
+
error?: {
|
|
15
|
+
code?: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
};
|
|
18
|
+
message?: string;
|
|
19
|
+
} | null, context?: unknown);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Erreur de RELAIS/infra : fonction injoignable (échec réseau), 502/503/504 de la
|
|
23
|
+
* passerelle, cold start échoué, corps non exploitable. Parité `FunctionsRelayError`
|
|
24
|
+
* de `@supabase/functions-js`. Sous-classe `AuraError`.
|
|
25
|
+
*/
|
|
26
|
+
declare class FunctionsRelayError extends AuraError {
|
|
27
|
+
readonly context: unknown;
|
|
28
|
+
constructor(status: number, body?: {
|
|
29
|
+
error?: {
|
|
30
|
+
code?: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
};
|
|
33
|
+
message?: string;
|
|
34
|
+
} | null, context?: unknown);
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
/** Type de réponse attendu de l'invocation (parité `@supabase/functions-js`). */
|
|
5
38
|
type FunctionsResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer';
|
|
6
39
|
declare class FunctionsService extends BaseService {
|
|
7
40
|
constructor(client: AuraClientLike);
|
|
41
|
+
/**
|
|
42
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
43
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
44
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
45
|
+
*/
|
|
8
46
|
private get prefix();
|
|
9
47
|
/**
|
|
10
48
|
* Invoque une edge function.
|
|
11
49
|
*
|
|
50
|
+
* Ce que la fonction REÇOIT (C20) : la requête vue par votre code est celle que vous
|
|
51
|
+
* décrivez ici — `method` (défaut `POST`), `path` (défaut `/`), `headers` et `body`
|
|
52
|
+
* APPLICATIF. Sur le runtime Deno :
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* // invoke('x', { method: 'PUT', path: '/items/1', headers: { 'x-src': 'sdk' }, body: { a: 1 } })
|
|
56
|
+
* Deno.serve(async (req) => {
|
|
57
|
+
* req.method // 'PUT'
|
|
58
|
+
* new URL(req.url).pathname // '/items/1'
|
|
59
|
+
* req.headers.get('x-src') // 'sdk'
|
|
60
|
+
* await req.json() // { a: 1 } ← le corps, PAS l'enveloppe
|
|
61
|
+
* })
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
12
64
|
* `responseType` (parité `@supabase/functions-js`, défaut `'json'`) :
|
|
13
65
|
* - `'json'` : chemin PIPELINE inchangé (refresh proactif centralisé, injection
|
|
14
66
|
* apikey/Authorization, dé-wrapping `{data,meta}`, retryConfig). Non-régression.
|
|
@@ -19,8 +71,9 @@ declare class FunctionsService extends BaseService {
|
|
|
19
71
|
*
|
|
20
72
|
* Erreurs typées (parité) : une réponse HTTP non-2xx → `FunctionsHttpError`, une
|
|
21
73
|
* panne de relais/infra (réseau, 502/503/504) → `FunctionsRelayError` ; les deux
|
|
22
|
-
* sous-classent `AuraError
|
|
23
|
-
* `
|
|
74
|
+
* sous-classent `AuraError`, ce qui est l'invariant du typage d'erreur du SDK :
|
|
75
|
+
* `isAuraError` reste vrai et le champ `error` reste lisible sur les deux. `timeout`/
|
|
76
|
+
* `signal` produisent le code `timeout`.
|
|
24
77
|
*/
|
|
25
78
|
invoke<T = unknown>(slug: string, options?: {
|
|
26
79
|
body?: unknown;
|
|
@@ -32,7 +85,14 @@ declare class FunctionsService extends BaseService {
|
|
|
32
85
|
responseType?: FunctionsResponseType;
|
|
33
86
|
}): Promise<AuraResponse<T>>;
|
|
34
87
|
list(): Promise<AuraResponse<FunctionDef[]>>;
|
|
35
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Lit une fonction par son **nom** ou par son **identifiant** (B10).
|
|
90
|
+
*
|
|
91
|
+
* Les deux clés sont acceptées : `list()` renvoie `{ id, name, … }` et les deux sont des
|
|
92
|
+
* points d'entrée naturels. En cas d'homonymie (une fonction PEUT s'appeler comme un UUID),
|
|
93
|
+
* le NOM l'emporte. Une valeur qui n'est ni l'un ni l'autre produit un 404 qui le dit.
|
|
94
|
+
*/
|
|
95
|
+
get(nameOrId: string): Promise<AuraResponse<FunctionDef>>;
|
|
36
96
|
listJobs(options?: {
|
|
37
97
|
status?: string;
|
|
38
98
|
limit?: number;
|
|
@@ -45,37 +105,4 @@ declare class FunctionsService extends BaseService {
|
|
|
45
105
|
deleteCronJob(name: string): Promise<AuraResponse<null>>;
|
|
46
106
|
}
|
|
47
107
|
|
|
48
|
-
declare function isRelayStatus(status: number): boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Erreur HTTP APPLICATIVE renvoyée par l'invocation d'une edge function
|
|
51
|
-
* (réponse HTTP non-2xx). Parité `FunctionsHttpError` de `@supabase/functions-js`.
|
|
52
|
-
* Sous-classe `AuraError` → `isAuraError()` reste vrai, `status` porte le code HTTP.
|
|
53
|
-
* `context` conserve le corps déjà lu (JSON/texte) pour inspection.
|
|
54
|
-
*/
|
|
55
|
-
declare class FunctionsHttpError extends AuraError {
|
|
56
|
-
readonly context: unknown;
|
|
57
|
-
constructor(status: number, body?: {
|
|
58
|
-
error?: {
|
|
59
|
-
code?: string;
|
|
60
|
-
message?: string;
|
|
61
|
-
};
|
|
62
|
-
message?: string;
|
|
63
|
-
} | null, context?: unknown);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Erreur de RELAIS/infra : fonction injoignable (échec réseau), 502/503/504 de la
|
|
67
|
-
* passerelle, cold start échoué, corps non exploitable. Parité `FunctionsRelayError`
|
|
68
|
-
* de `@supabase/functions-js`. Sous-classe `AuraError`.
|
|
69
|
-
*/
|
|
70
|
-
declare class FunctionsRelayError extends AuraError {
|
|
71
|
-
readonly context: unknown;
|
|
72
|
-
constructor(status: number, body?: {
|
|
73
|
-
error?: {
|
|
74
|
-
code?: string;
|
|
75
|
-
message?: string;
|
|
76
|
-
};
|
|
77
|
-
message?: string;
|
|
78
|
-
} | null, context?: unknown);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
108
|
export { FunctionsHttpError, FunctionsRelayError, type FunctionsResponseType, FunctionsService, isRelayStatus };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuraError, BaseService, isTokenExpired, refreshOnce, isAbortError } from '@aurabase/core';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/errors.ts
|
|
4
4
|
var RELAY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
5
5
|
function isRelayStatus(status) {
|
|
6
6
|
return status === 0 || RELAY_STATUSES.has(status);
|
|
@@ -27,19 +27,36 @@ function toFunctionsError(err, context) {
|
|
|
27
27
|
const body = err.details != null ? err.details : { error: { code: err.code, message: err.message } };
|
|
28
28
|
return isRelayStatus(err.status) ? new FunctionsRelayError(err.status, body, err.details) : new FunctionsHttpError(err.status, body, err.details);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
// src/FunctionsService.ts
|
|
32
30
|
var FunctionsService = class extends BaseService {
|
|
33
31
|
constructor(client) {
|
|
34
32
|
super(client);
|
|
35
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
36
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
37
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
38
|
+
*/
|
|
36
39
|
get prefix() {
|
|
37
|
-
return
|
|
40
|
+
return "/v1/functions";
|
|
38
41
|
}
|
|
39
42
|
// -- Function invocation ----------------------------------------------------
|
|
40
43
|
/**
|
|
41
44
|
* Invoque une edge function.
|
|
42
45
|
*
|
|
46
|
+
* Ce que la fonction REÇOIT (C20) : la requête vue par votre code est celle que vous
|
|
47
|
+
* décrivez ici — `method` (défaut `POST`), `path` (défaut `/`), `headers` et `body`
|
|
48
|
+
* APPLICATIF. Sur le runtime Deno :
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* // invoke('x', { method: 'PUT', path: '/items/1', headers: { 'x-src': 'sdk' }, body: { a: 1 } })
|
|
52
|
+
* Deno.serve(async (req) => {
|
|
53
|
+
* req.method // 'PUT'
|
|
54
|
+
* new URL(req.url).pathname // '/items/1'
|
|
55
|
+
* req.headers.get('x-src') // 'sdk'
|
|
56
|
+
* await req.json() // { a: 1 } ← le corps, PAS l'enveloppe
|
|
57
|
+
* })
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
43
60
|
* `responseType` (parité `@supabase/functions-js`, défaut `'json'`) :
|
|
44
61
|
* - `'json'` : chemin PIPELINE inchangé (refresh proactif centralisé, injection
|
|
45
62
|
* apikey/Authorization, dé-wrapping `{data,meta}`, retryConfig). Non-régression.
|
|
@@ -50,8 +67,9 @@ var FunctionsService = class extends BaseService {
|
|
|
50
67
|
*
|
|
51
68
|
* Erreurs typées (parité) : une réponse HTTP non-2xx → `FunctionsHttpError`, une
|
|
52
69
|
* panne de relais/infra (réseau, 502/503/504) → `FunctionsRelayError` ; les deux
|
|
53
|
-
* sous-classent `AuraError
|
|
54
|
-
* `
|
|
70
|
+
* sous-classent `AuraError`, ce qui est l'invariant du typage d'erreur du SDK :
|
|
71
|
+
* `isAuraError` reste vrai et le champ `error` reste lisible sur les deux. `timeout`/
|
|
72
|
+
* `signal` produisent le code `timeout`.
|
|
55
73
|
*/
|
|
56
74
|
async invoke(slug, options) {
|
|
57
75
|
const controller = new AbortController();
|
|
@@ -67,7 +85,12 @@ var FunctionsService = class extends BaseService {
|
|
|
67
85
|
if (timeoutId) clearTimeout(timeoutId);
|
|
68
86
|
if (userSignal) userSignal.removeEventListener("abort", onAbort);
|
|
69
87
|
};
|
|
70
|
-
const envelope = {
|
|
88
|
+
const envelope = {
|
|
89
|
+
method: options?.method,
|
|
90
|
+
path: options?.path,
|
|
91
|
+
headers: options?.headers,
|
|
92
|
+
body: options?.body
|
|
93
|
+
};
|
|
71
94
|
const responseType = options?.responseType ?? "json";
|
|
72
95
|
const timeoutError = () => ({
|
|
73
96
|
data: null,
|
|
@@ -77,8 +100,9 @@ var FunctionsService = class extends BaseService {
|
|
|
77
100
|
});
|
|
78
101
|
try {
|
|
79
102
|
if (responseType === "json") {
|
|
103
|
+
const hasCanceller = options?.timeout !== void 0 || userSignal !== void 0;
|
|
80
104
|
const res2 = await this.request("POST", `${this.prefix}/${slug}/invoke`, envelope, {
|
|
81
|
-
signal: controller.signal
|
|
105
|
+
...hasCanceller ? { signal: controller.signal } : {}
|
|
82
106
|
});
|
|
83
107
|
if (res2.error && controller.signal.aborted) return timeoutError();
|
|
84
108
|
if (res2.error) return { data: null, error: toFunctionsError(res2.error) };
|
|
@@ -123,7 +147,10 @@ var FunctionsService = class extends BaseService {
|
|
|
123
147
|
return {
|
|
124
148
|
data: null,
|
|
125
149
|
error: new FunctionsRelayError(0, {
|
|
126
|
-
error: {
|
|
150
|
+
error: {
|
|
151
|
+
code: "network_error",
|
|
152
|
+
message: err instanceof Error ? err.message : "Function relay error"
|
|
153
|
+
}
|
|
127
154
|
})
|
|
128
155
|
};
|
|
129
156
|
} finally {
|
|
@@ -134,8 +161,15 @@ var FunctionsService = class extends BaseService {
|
|
|
134
161
|
async list() {
|
|
135
162
|
return this.request("GET", `${this.prefix}`);
|
|
136
163
|
}
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Lit une fonction par son **nom** ou par son **identifiant** (B10).
|
|
166
|
+
*
|
|
167
|
+
* Les deux clés sont acceptées : `list()` renvoie `{ id, name, … }` et les deux sont des
|
|
168
|
+
* points d'entrée naturels. En cas d'homonymie (une fonction PEUT s'appeler comme un UUID),
|
|
169
|
+
* le NOM l'emporte. Une valeur qui n'est ni l'un ni l'autre produit un 404 qui le dit.
|
|
170
|
+
*/
|
|
171
|
+
async get(nameOrId) {
|
|
172
|
+
return this.request("GET", `${this.prefix}/${nameOrId}`);
|
|
139
173
|
}
|
|
140
174
|
// -- Jobs -------------------------------------------------------------------
|
|
141
175
|
async listJobs(options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/functions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Service edge functions du SDK Aurabase (invoke, jobs asynchrones, cron)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -45,9 +45,10 @@
|
|
|
45
45
|
"functions"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@aurabase/core": "0.
|
|
48
|
+
"@aurabase/core": "0.5.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
+
"@types/node": "20.14.15",
|
|
51
52
|
"tsup": "^8.0.0",
|
|
52
53
|
"typescript": "^5.5.0",
|
|
53
54
|
"vitest": "^4.1.6"
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"scripts": {
|
|
56
57
|
"build": "tsup",
|
|
57
58
|
"test": "vitest run --passWithNoTests",
|
|
58
|
-
"typecheck": "tsc --noEmit"
|
|
59
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
60
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json"
|
|
59
61
|
}
|
|
60
62
|
}
|