@webiny/api-core 6.5.0-beta.4 → 6.5.0-beta.6
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/ApiCoreFeature.js +2 -0
- package/ApiCoreFeature.js.map +1 -1
- package/features/ai/AiModelRegistry.d.ts +3 -1
- package/features/ai/AiModelRegistry.js +14 -6
- package/features/ai/AiModelRegistry.js.map +1 -1
- package/features/ai/DEVELOPERS.md +47 -12
- package/features/ai/RemoteAiModelCatalog.d.ts +13 -0
- package/features/ai/RemoteAiModelCatalog.js +69 -0
- package/features/ai/RemoteAiModelCatalog.js.map +1 -0
- package/features/ai/abstractions.d.ts +14 -0
- package/features/ai/abstractions.js +2 -1
- package/features/ai/abstractions.js.map +1 -1
- package/features/ai/feature.js +2 -0
- package/features/ai/feature.js.map +1 -1
- package/features/ai/index.d.ts +2 -2
- package/features/ai/index.js +1 -1
- package/features/debugger/__tests__/serialize.test.js +1 -1
- package/features/debugger/__tests__/serialize.test.js.map +1 -1
- package/features/httpClient/HttpClient.d.ts +7 -0
- package/features/httpClient/HttpClient.js +72 -0
- package/features/httpClient/HttpClient.js.map +1 -0
- package/features/httpClient/HttpRequestError.d.ts +10 -0
- package/features/httpClient/HttpRequestError.js +13 -0
- package/features/httpClient/HttpRequestError.js.map +1 -0
- package/features/httpClient/HttpStatusError.d.ts +12 -0
- package/features/httpClient/HttpStatusError.js +12 -0
- package/features/httpClient/HttpStatusError.js.map +1 -0
- package/features/httpClient/abstractions.d.ts +32 -0
- package/features/httpClient/abstractions.js +5 -0
- package/features/httpClient/abstractions.js.map +1 -0
- package/features/httpClient/feature.d.ts +4 -0
- package/features/httpClient/feature.js +11 -0
- package/features/httpClient/feature.js.map +1 -0
- package/features/httpClient/index.d.ts +3 -0
- package/features/httpClient/index.js +3 -0
- package/features/users/shared/loaders.js.map +1 -1
- package/idp/JwksCache.d.ts +7 -2
- package/idp/JwksCache.js +43 -16
- package/idp/JwksCache.js.map +1 -1
- package/idp/feature.js +1 -2
- package/idp/feature.js.map +1 -1
- package/package.json +23 -23
package/ApiCoreFeature.js
CHANGED
|
@@ -13,12 +13,14 @@ import { EncryptionFeature } from "./features/encryption/feature.js";
|
|
|
13
13
|
import { FeatureFlagsFeature } from "./features/featureFlags/feature.js";
|
|
14
14
|
import { MaskerFeature } from "./features/masker/feature.js";
|
|
15
15
|
import { AiFeature } from "./features/ai/feature.js";
|
|
16
|
+
import { HttpClientFeature } from "./features/httpClient/feature.js";
|
|
16
17
|
import { NullWebhookDispatcher } from "./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js";
|
|
17
18
|
import { WebhookProviderFeature } from "./features/webhooks/index.js";
|
|
18
19
|
const ApiCoreFeature = createFeature({
|
|
19
20
|
name: "ApiCore",
|
|
20
21
|
register (container, config) {
|
|
21
22
|
MaskerFeature.register(container);
|
|
23
|
+
HttpClientFeature.register(container);
|
|
22
24
|
AiFeature.register(container);
|
|
23
25
|
LoggerFeature.register(container);
|
|
24
26
|
EventPublisherFeature.register(container);
|
package/ApiCoreFeature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCoreFeature.js","sources":["../src/ApiCoreFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { Container } from \"@webiny/di\";\nimport { EventPublisherFeature } from \"~/features/eventPublisher/feature.js\";\nimport { LoggerFeature } from \"~/features/logger/feature.js\";\nimport { SecurityFeature } from \"~/features/security/SecurityFeature.js\";\nimport { SystemFeature } from \"~/features/system/SystemFeature.js\";\nimport { TenancyFeature } from \"./features/tenancy/TenancyFeature.js\";\nimport { AdminUsersFeature } from \"~/features/users/AdminUsersFeature.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\nimport { IdpAuthenticatorFeature } from \"~/idp/feature.js\";\nimport { KeyValueStoreFeature } from \"~/features/keyValueStore/feature.js\";\nimport { BuildParamsFeature } from \"~/features/buildParams/feature.js\";\nimport { DebuggerFeature } from \"~/features/debugger/feature.js\";\nimport { EncryptionFeature } from \"~/features/encryption/feature.js\";\nimport { FeatureFlagsFeature } from \"~/features/featureFlags/feature.js\";\nimport { MaskerFeature } from \"~/features/masker/feature.js\";\nimport { AiFeature } from \"~/features/ai/feature.js\";\nimport { NullWebhookDispatcher } from \"./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js\";\nimport { WebhookProviderFeature } from \"~/features/webhooks/index.js\";\n\nexport const ApiCoreFeature = createFeature({\n name: \"ApiCore\",\n register(container: Container, config: ApiCoreStorageOperations) {\n // Register features\n MaskerFeature.register(container);\n AiFeature.register(container);\n LoggerFeature.register(container);\n EventPublisherFeature.register(container);\n BuildParamsFeature.register(container);\n DebuggerFeature.register(container);\n EncryptionFeature.register(container);\n FeatureFlagsFeature.register(container);\n TenancyFeature.register(container, config.tenancyStorageOperations);\n SecurityFeature.register(container, config.securityStorageOperations);\n AdminUsersFeature.register(container, config.usersStorageOperations);\n KeyValueStoreFeature.register(container, config.keyValueStorageOperations);\n SystemFeature.register(container);\n IdpAuthenticatorFeature.register(container);\n container.register(NullWebhookDispatcher).inSingletonScope();\n WebhookProviderFeature.register(container);\n }\n});\n"],"names":["ApiCoreFeature","createFeature","container","config","MaskerFeature","AiFeature","LoggerFeature","EventPublisherFeature","BuildParamsFeature","DebuggerFeature","EncryptionFeature","FeatureFlagsFeature","TenancyFeature","SecurityFeature","AdminUsersFeature","KeyValueStoreFeature","SystemFeature","IdpAuthenticatorFeature","NullWebhookDispatcher","WebhookProviderFeature"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ApiCoreFeature.js","sources":["../src/ApiCoreFeature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { Container } from \"@webiny/di\";\nimport { EventPublisherFeature } from \"~/features/eventPublisher/feature.js\";\nimport { LoggerFeature } from \"~/features/logger/feature.js\";\nimport { SecurityFeature } from \"~/features/security/SecurityFeature.js\";\nimport { SystemFeature } from \"~/features/system/SystemFeature.js\";\nimport { TenancyFeature } from \"./features/tenancy/TenancyFeature.js\";\nimport { AdminUsersFeature } from \"~/features/users/AdminUsersFeature.js\";\nimport type { ApiCoreStorageOperations } from \"~/types/core.js\";\nimport { IdpAuthenticatorFeature } from \"~/idp/feature.js\";\nimport { KeyValueStoreFeature } from \"~/features/keyValueStore/feature.js\";\nimport { BuildParamsFeature } from \"~/features/buildParams/feature.js\";\nimport { DebuggerFeature } from \"~/features/debugger/feature.js\";\nimport { EncryptionFeature } from \"~/features/encryption/feature.js\";\nimport { FeatureFlagsFeature } from \"~/features/featureFlags/feature.js\";\nimport { MaskerFeature } from \"~/features/masker/feature.js\";\nimport { AiFeature } from \"~/features/ai/feature.js\";\nimport { HttpClientFeature } from \"~/features/httpClient/feature.js\";\nimport { NullWebhookDispatcher } from \"./features/webhooks/WebhookDispatcher/NullWebhookDispatcher.js\";\nimport { WebhookProviderFeature } from \"~/features/webhooks/index.js\";\n\nexport const ApiCoreFeature = createFeature({\n name: \"ApiCore\",\n register(container: Container, config: ApiCoreStorageOperations) {\n // Register features\n MaskerFeature.register(container);\n HttpClientFeature.register(container);\n AiFeature.register(container);\n LoggerFeature.register(container);\n EventPublisherFeature.register(container);\n BuildParamsFeature.register(container);\n DebuggerFeature.register(container);\n EncryptionFeature.register(container);\n FeatureFlagsFeature.register(container);\n TenancyFeature.register(container, config.tenancyStorageOperations);\n SecurityFeature.register(container, config.securityStorageOperations);\n AdminUsersFeature.register(container, config.usersStorageOperations);\n KeyValueStoreFeature.register(container, config.keyValueStorageOperations);\n SystemFeature.register(container);\n IdpAuthenticatorFeature.register(container);\n container.register(NullWebhookDispatcher).inSingletonScope();\n WebhookProviderFeature.register(container);\n }\n});\n"],"names":["ApiCoreFeature","createFeature","container","config","MaskerFeature","HttpClientFeature","AiFeature","LoggerFeature","EventPublisherFeature","BuildParamsFeature","DebuggerFeature","EncryptionFeature","FeatureFlagsFeature","TenancyFeature","SecurityFeature","AdminUsersFeature","KeyValueStoreFeature","SystemFeature","IdpAuthenticatorFeature","NullWebhookDispatcher","WebhookProviderFeature"],"mappings":";;;;;;;;;;;;;;;;;;AAqBO,MAAMA,iBAAiBC,cAAc;IACxC,MAAM;IACN,UAASC,SAAoB,EAAEC,MAAgC;QAE3DC,cAAc,QAAQ,CAACF;QACvBG,kBAAkB,QAAQ,CAACH;QAC3BI,UAAU,QAAQ,CAACJ;QACnBK,cAAc,QAAQ,CAACL;QACvBM,sBAAsB,QAAQ,CAACN;QAC/BO,mBAAmB,QAAQ,CAACP;QAC5BQ,gBAAgB,QAAQ,CAACR;QACzBS,kBAAkB,QAAQ,CAACT;QAC3BU,oBAAoB,QAAQ,CAACV;QAC7BW,eAAe,QAAQ,CAACX,WAAWC,OAAO,wBAAwB;QAClEW,gBAAgB,QAAQ,CAACZ,WAAWC,OAAO,yBAAyB;QACpEY,kBAAkB,QAAQ,CAACb,WAAWC,OAAO,sBAAsB;QACnEa,qBAAqB,QAAQ,CAACd,WAAWC,OAAO,yBAAyB;QACzEc,cAAc,QAAQ,CAACf;QACvBgB,wBAAwB,QAAQ,CAAChB;QACjCA,UAAU,QAAQ,CAACiB,uBAAuB,gBAAgB;QAC1DC,uBAAuB,QAAQ,CAAClB;IACpC;AACJ"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { AiModelRegistry as AiModelRegistryAbstraction } from "./abstractions.js";
|
|
2
2
|
import { AiSdkFactory } from "./abstractions.js";
|
|
3
|
+
import { AiModelCatalog } from "./abstractions.js";
|
|
3
4
|
import type { AiModel } from "./abstractions.js";
|
|
4
5
|
declare class AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {
|
|
5
6
|
private readonly sdkFactories;
|
|
6
|
-
|
|
7
|
+
private readonly catalog;
|
|
8
|
+
constructor(sdkFactories: AiSdkFactory.Interface[], catalog: AiModelCatalog.Interface);
|
|
7
9
|
listModels(): Promise<AiModel[]>;
|
|
8
10
|
}
|
|
9
11
|
export declare const AiModelRegistry: typeof AiModelRegistryImpl & {
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import { AiModelRegistry, AiSdkFactory } from "./abstractions.js";
|
|
1
|
+
import { AiModelCatalog, AiModelRegistry, AiSdkFactory } from "./abstractions.js";
|
|
2
2
|
class AiModelRegistryImpl {
|
|
3
|
-
constructor(sdkFactories){
|
|
3
|
+
constructor(sdkFactories, catalog){
|
|
4
4
|
this.sdkFactories = sdkFactories;
|
|
5
|
+
this.catalog = catalog;
|
|
5
6
|
}
|
|
6
7
|
async listModels() {
|
|
7
|
-
|
|
8
|
+
const providers = await this.catalog.listProviders();
|
|
9
|
+
return this.sdkFactories.flatMap((factory)=>{
|
|
10
|
+
const provider = providers?.find((p)=>p.id === factory.id);
|
|
11
|
+
const providerName = provider?.name ?? factory.name;
|
|
12
|
+
const models = provider?.models ?? factory.models;
|
|
13
|
+
return models.map((m)=>({
|
|
8
14
|
providerId: factory.id,
|
|
9
|
-
providerName
|
|
15
|
+
providerName,
|
|
10
16
|
modelId: m.id,
|
|
11
17
|
modelName: m.name,
|
|
12
18
|
deprecated: m.deprecated,
|
|
13
19
|
endOfLife: m.endOfLife
|
|
14
|
-
}))
|
|
20
|
+
}));
|
|
21
|
+
});
|
|
15
22
|
}
|
|
16
23
|
}
|
|
17
24
|
const AiModelRegistry_AiModelRegistry = AiModelRegistry.createImplementation({
|
|
@@ -22,7 +29,8 @@ const AiModelRegistry_AiModelRegistry = AiModelRegistry.createImplementation({
|
|
|
22
29
|
{
|
|
23
30
|
multiple: true
|
|
24
31
|
}
|
|
25
|
-
]
|
|
32
|
+
],
|
|
33
|
+
AiModelCatalog
|
|
26
34
|
]
|
|
27
35
|
});
|
|
28
36
|
export { AiModelRegistry_AiModelRegistry as AiModelRegistry };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/ai/AiModelRegistry.js","sources":["../../../src/features/ai/AiModelRegistry.ts"],"sourcesContent":["import { AiModelRegistry as AiModelRegistryAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport type { AiModel } from \"./abstractions.js\";\n\nclass AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {\n constructor(private readonly sdkFactories: AiSdkFactory.Interface[]) {}\n\n async listModels(): Promise<AiModel[]> {\n return this.sdkFactories.flatMap(factory
|
|
1
|
+
{"version":3,"file":"features/ai/AiModelRegistry.js","sources":["../../../src/features/ai/AiModelRegistry.ts"],"sourcesContent":["import { AiModelRegistry as AiModelRegistryAbstraction } from \"./abstractions.js\";\nimport { AiSdkFactory } from \"./abstractions.js\";\nimport { AiModelCatalog } from \"./abstractions.js\";\nimport type { AiModel } from \"./abstractions.js\";\n\nclass AiModelRegistryImpl implements AiModelRegistryAbstraction.Interface {\n constructor(\n private readonly sdkFactories: AiSdkFactory.Interface[],\n private readonly catalog: AiModelCatalog.Interface\n ) {}\n\n async listModels(): Promise<AiModel[]> {\n const providers = await this.catalog.listProviders();\n\n /*\n * A provider is only offered when an SDK factory can serve it. The catalog decides its\n * models; a factory the catalog doesn't know (a custom SDK), or an unreachable catalog,\n * falls back to the factory's own list.\n */\n return this.sdkFactories.flatMap(factory => {\n const provider = providers?.find(p => p.id === factory.id);\n const providerName = provider?.name ?? factory.name;\n const models = provider?.models ?? factory.models;\n\n return models.map(m => ({\n providerId: factory.id,\n providerName,\n modelId: m.id,\n modelName: m.name,\n deprecated: m.deprecated,\n endOfLife: m.endOfLife\n }));\n });\n }\n}\n\nexport const AiModelRegistry = AiModelRegistryAbstraction.createImplementation({\n implementation: AiModelRegistryImpl,\n dependencies: [[AiSdkFactory, { multiple: true }], AiModelCatalog]\n});\n"],"names":["AiModelRegistryImpl","sdkFactories","catalog","providers","factory","provider","p","providerName","models","m","AiModelRegistry","AiModelRegistryAbstraction","AiSdkFactory","AiModelCatalog"],"mappings":";AAKA,MAAMA;IACF,YACqBC,YAAsC,EACtCC,OAAiC,CACpD;aAFmBD,YAAY,GAAZA;aACAC,OAAO,GAAPA;IAClB;IAEH,MAAM,aAAiC;QACnC,MAAMC,YAAY,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa;QAOlD,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAACC,CAAAA;YAC7B,MAAMC,WAAWF,WAAW,KAAKG,CAAAA,IAAKA,EAAE,EAAE,KAAKF,QAAQ,EAAE;YACzD,MAAMG,eAAeF,UAAU,QAAQD,QAAQ,IAAI;YACnD,MAAMI,SAASH,UAAU,UAAUD,QAAQ,MAAM;YAEjD,OAAOI,OAAO,GAAG,CAACC,CAAAA,IAAM;oBACpB,YAAYL,QAAQ,EAAE;oBACtBG;oBACA,SAASE,EAAE,EAAE;oBACb,WAAWA,EAAE,IAAI;oBACjB,YAAYA,EAAE,UAAU;oBACxB,WAAWA,EAAE,SAAS;gBAC1B;QACJ;IACJ;AACJ;AAEO,MAAMC,kCAAkBC,gBAAAA,oBAA+C,CAAC;IAC3E,gBAAgBX;IAChB,cAAc;QAAC;YAACY;YAAc;gBAAE,UAAU;YAAK;SAAE;QAAEC;KAAe;AACtE"}
|
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
# AI Models
|
|
2
2
|
|
|
3
|
-
This directory contains the AI SDK factory implementations that
|
|
3
|
+
This directory contains the AI SDK factory implementations that serve the models available in Webiny's AI Powerups.
|
|
4
|
+
|
|
5
|
+
## Where the model list comes from
|
|
6
|
+
|
|
7
|
+
`AiModelRegistry` takes the published catalog at `https://api.webiny.com/ai/models` (`RemoteAiModelCatalog`, overridable through `WCP_API_URL`) and intersects it with the registered SDK factories:
|
|
8
|
+
|
|
9
|
+
- A catalog provider with no SDK factory is not offered. The SDK factory is the last gate.
|
|
10
|
+
- For a provider that has a factory, the catalog's models and names replace the factory's own list.
|
|
11
|
+
- A factory the catalog doesn't know (a custom SDK) keeps its own `models`.
|
|
12
|
+
- If the catalog can't be loaded (timeout, error status, invalid body), every factory falls back to its own `models`.
|
|
13
|
+
|
|
14
|
+
The catalog is cached for an hour per process. A failed load is retried after a minute. The first AI call on a cold Lambda waits for the fetch. If the catalog doesn't answer within 3 seconds, the fetch is aborted and the factories' own lists are used.
|
|
15
|
+
|
|
16
|
+
So adding or deprecating a model for an existing provider is a catalog change and needs no release. The `*_MODELS` arrays below are the offline fallback; keep them roughly in sync.
|
|
17
|
+
|
|
18
|
+
The catalog response looks like this. Dates are ISO strings, and fields the schema doesn't know (`api`, `supports`, ...) are dropped:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"providers": [
|
|
23
|
+
{
|
|
24
|
+
"id": "openai",
|
|
25
|
+
"name": "OpenAI",
|
|
26
|
+
"models": [
|
|
27
|
+
{ "id": "o3", "name": "o3", "deprecated": "2026-06-11", "endOfLife": "2026-12-11" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A response that doesn't match the schema counts as a failed load, so one bad entry drops the whole catalog and every factory falls back to its own list.
|
|
4
35
|
|
|
5
36
|
## Selection criteria
|
|
6
37
|
|
|
7
|
-
Every model
|
|
38
|
+
Every model in the catalog or in a factory **must** support:
|
|
8
39
|
|
|
9
40
|
- **Tool/function calling** — AI Powerups relies on tools for structured output (content generation, entry comparison, image enrichment, etc.). Models without tool support will break these features at runtime.
|
|
10
41
|
- **Chat/text generation** — only conversational models are listed; embedding, image-generation, TTS, and other specialized models are excluded.
|
|
@@ -21,7 +52,7 @@ Each model entry in `IAiSdkModel` supports two optional `Date` fields:
|
|
|
21
52
|
|
|
22
53
|
Set these fields when the provider announces deprecation or end-of-life dates. The UI can use them to warn users about upcoming shutdowns or steer them toward replacements.
|
|
23
54
|
|
|
24
|
-
When a model reaches its `endOfLife` date, remove it from the factory in the next release
|
|
55
|
+
When a model reaches its `endOfLife` date, remove it from the catalog, which takes effect within an hour. Remove it from the factory in the next release too, or deployments that can't reach the catalog will keep offering a model that no longer works.
|
|
25
56
|
|
|
26
57
|
## Providers
|
|
27
58
|
|
|
@@ -103,24 +134,28 @@ Source: https://ai.google.dev/gemini-api/docs/deprecations
|
|
|
103
134
|
## Adding a new model
|
|
104
135
|
|
|
105
136
|
1. Verify the model supports tool/function calling.
|
|
106
|
-
2. Add
|
|
107
|
-
3.
|
|
108
|
-
4.
|
|
137
|
+
2. Add it to the catalog at `https://api.webiny.com/ai/models`. That is what users see.
|
|
138
|
+
3. Add an entry to the `*_MODELS` array in the corresponding factory file. The `id` must match the provider's API model ID exactly.
|
|
139
|
+
4. If the model has known deprecation or end-of-life dates, set `deprecated` and/or `endOfLife`: ISO strings (`"YYYY-MM-DD"`) in the catalog, `new Date("YYYY-MM-DD")` in the factory.
|
|
140
|
+
5. Update the table in this file.
|
|
109
141
|
|
|
110
142
|
## Adding a new provider
|
|
111
143
|
|
|
112
|
-
|
|
144
|
+
A new provider needs a release, because the catalog can only offer providers that have an SDK factory.
|
|
145
|
+
|
|
146
|
+
1. Create a new `<Provider>SdkFactory.ts` implementing `AiSdkFactory`. Its `id` is the key the catalog matches on.
|
|
113
147
|
2. Add the `@ai-sdk/<provider>` dependency via `yarn add` in `packages/api-core`.
|
|
114
148
|
3. Register the factory in `feature.ts`.
|
|
115
|
-
4.
|
|
149
|
+
4. Add the provider to the catalog under the same `id`. Until you do, the factory's own `models` are used.
|
|
150
|
+
5. Update this file.
|
|
116
151
|
|
|
117
152
|
## Updating models
|
|
118
153
|
|
|
119
|
-
Check the provider docs periodically and update the lists:
|
|
154
|
+
Check the provider docs periodically and update the catalog first, then the factory lists:
|
|
120
155
|
|
|
121
156
|
- **New models**: add to the top of the active section.
|
|
122
157
|
- **Deprecated models**: set `deprecated` and `endOfLife` dates, move to the bottom of the array.
|
|
123
|
-
- **End-of-life reached**: remove from the factory. Keeping a dead model causes runtime failures.
|
|
124
|
-
- **Never remove a model before its end-of-life date** — users may have presets referencing it, and removing it breaks `ProvidersHandler` validation on save.
|
|
158
|
+
- **End-of-life reached**: remove from the catalog and the factory. Keeping a dead model causes runtime failures.
|
|
159
|
+
- **Never remove a model before its end-of-life date** — users may have presets referencing it, and removing it breaks `ProvidersHandler` validation on save. This matters more for the catalog than for the factory: `Ai` rejects any model the registry doesn't list, so dropping a model from the catalog breaks calls that use it in every deployment within the hour.
|
|
125
160
|
|
|
126
|
-
Last updated:
|
|
161
|
+
Last updated: October 2026.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AiModelCatalog as AiModelCatalogAbstraction } from "./abstractions.js";
|
|
2
|
+
import type { AiCatalogProvider } from "./abstractions.js";
|
|
3
|
+
import { Logger } from "../../features/logger/index.js";
|
|
4
|
+
import { HttpClient } from "../../features/httpClient/index.js";
|
|
5
|
+
declare class RemoteAiModelCatalogImpl implements AiModelCatalogAbstraction.Interface {
|
|
6
|
+
private readonly httpClient;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(httpClient: HttpClient.Interface, logger: Logger.Interface);
|
|
9
|
+
listProviders(): Promise<AiCatalogProvider[] | undefined>;
|
|
10
|
+
private fetchProviders;
|
|
11
|
+
}
|
|
12
|
+
export declare const RemoteAiModelCatalog: import("@webiny/di").Implementation<typeof RemoteAiModelCatalogImpl>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createImplementation } from "@webiny/feature/api";
|
|
3
|
+
import { getWcpApiUrl } from "@webiny/wcp";
|
|
4
|
+
import { AiModelCatalog } from "./abstractions.js";
|
|
5
|
+
import { Logger } from "../logger/index.js";
|
|
6
|
+
import { HttpClient } from "../httpClient/index.js";
|
|
7
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
8
|
+
const SUCCESS_TTL_MS = 3600000;
|
|
9
|
+
const FAILURE_TTL_MS = 60000;
|
|
10
|
+
const optionalDate = z.coerce.date().optional();
|
|
11
|
+
const catalogSchema = z.object({
|
|
12
|
+
providers: z.array(z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
models: z.array(z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
deprecated: optionalDate,
|
|
19
|
+
endOfLife: optionalDate
|
|
20
|
+
}))
|
|
21
|
+
}))
|
|
22
|
+
});
|
|
23
|
+
let cached;
|
|
24
|
+
class RemoteAiModelCatalogImpl {
|
|
25
|
+
constructor(httpClient, logger){
|
|
26
|
+
this.httpClient = httpClient;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
listProviders() {
|
|
30
|
+
if (cached && cached.expiresAt > Date.now()) return cached.providers;
|
|
31
|
+
const entry = {
|
|
32
|
+
expiresAt: Date.now() + SUCCESS_TTL_MS,
|
|
33
|
+
providers: this.fetchProviders().then((providers)=>{
|
|
34
|
+
if (!providers) entry.expiresAt = Date.now() + FAILURE_TTL_MS;
|
|
35
|
+
return providers;
|
|
36
|
+
})
|
|
37
|
+
};
|
|
38
|
+
cached = entry;
|
|
39
|
+
return entry.providers;
|
|
40
|
+
}
|
|
41
|
+
async fetchProviders() {
|
|
42
|
+
const url = getWcpApiUrl("/ai/models");
|
|
43
|
+
const response = await this.httpClient.requestJson({
|
|
44
|
+
url,
|
|
45
|
+
timeoutMs: FETCH_TIMEOUT_MS
|
|
46
|
+
});
|
|
47
|
+
if (response.isFail()) return void this.logger.warn({
|
|
48
|
+
url,
|
|
49
|
+
error: response.error
|
|
50
|
+
}, "Failed to load AI models.");
|
|
51
|
+
const result = catalogSchema.safeParse(response.value);
|
|
52
|
+
if (!result.success) return void this.logger.warn({
|
|
53
|
+
url,
|
|
54
|
+
error: result.error
|
|
55
|
+
}, "Received invalid AI models.");
|
|
56
|
+
return result.data.providers;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const RemoteAiModelCatalog = createImplementation({
|
|
60
|
+
abstraction: AiModelCatalog,
|
|
61
|
+
implementation: RemoteAiModelCatalogImpl,
|
|
62
|
+
dependencies: [
|
|
63
|
+
HttpClient,
|
|
64
|
+
Logger
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
export { RemoteAiModelCatalog };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=RemoteAiModelCatalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/ai/RemoteAiModelCatalog.js","sources":["../../../src/features/ai/RemoteAiModelCatalog.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { createImplementation } from \"@webiny/feature/api\";\nimport { getWcpApiUrl } from \"@webiny/wcp\";\nimport { AiModelCatalog as AiModelCatalogAbstraction } from \"./abstractions.js\";\nimport type { AiCatalogProvider } from \"./abstractions.js\";\nimport { Logger } from \"~/features/logger/index.js\";\nimport { HttpClient } from \"~/features/httpClient/index.js\";\n\nconst FETCH_TIMEOUT_MS = 3000;\nconst SUCCESS_TTL_MS = 60 * 60 * 1000;\nconst FAILURE_TTL_MS = 60 * 1000;\n\nconst optionalDate = z.coerce.date().optional();\n\nconst catalogSchema = z.object({\n providers: z.array(\n z.object({\n id: z.string(),\n name: z.string(),\n models: z.array(\n z.object({\n id: z.string(),\n name: z.string(),\n deprecated: optionalDate,\n endOfLife: optionalDate\n })\n )\n })\n )\n});\n\ninterface CachedCatalog {\n expiresAt: number;\n providers: Promise<AiCatalogProvider[] | undefined>;\n}\n\n/*\n * Process-wide on purpose: the catalog is the same for every tenant and request, and\n * `Ai.resolveLanguageModel()` reads it on every call, so a warm Lambda should fetch it once.\n */\nlet cached: CachedCatalog | undefined;\n\nclass RemoteAiModelCatalogImpl implements AiModelCatalogAbstraction.Interface {\n constructor(\n private readonly httpClient: HttpClient.Interface,\n private readonly logger: Logger.Interface\n ) {}\n\n listProviders(): Promise<AiCatalogProvider[] | undefined> {\n if (cached && cached.expiresAt > Date.now()) {\n return cached.providers;\n }\n\n const entry: CachedCatalog = {\n expiresAt: Date.now() + SUCCESS_TTL_MS,\n providers: this.fetchProviders().then(providers => {\n if (!providers) {\n entry.expiresAt = Date.now() + FAILURE_TTL_MS;\n }\n return providers;\n })\n };\n cached = entry;\n\n return entry.providers;\n }\n\n private async fetchProviders(): Promise<AiCatalogProvider[] | undefined> {\n const url = getWcpApiUrl(\"/ai/models\");\n\n const response = await this.httpClient.requestJson({ url, timeoutMs: FETCH_TIMEOUT_MS });\n if (response.isFail()) {\n this.logger.warn({ url, error: response.error }, \"Failed to load AI models.\");\n return undefined;\n }\n\n const result = catalogSchema.safeParse(response.value);\n if (!result.success) {\n this.logger.warn({ url, error: result.error }, \"Received invalid AI models.\");\n return undefined;\n }\n\n return result.data.providers;\n }\n}\n\nexport const RemoteAiModelCatalog = createImplementation({\n abstraction: AiModelCatalogAbstraction,\n implementation: RemoteAiModelCatalogImpl,\n dependencies: [HttpClient, Logger]\n});\n"],"names":["FETCH_TIMEOUT_MS","SUCCESS_TTL_MS","FAILURE_TTL_MS","optionalDate","z","catalogSchema","cached","RemoteAiModelCatalogImpl","httpClient","logger","Date","entry","providers","url","getWcpApiUrl","response","result","RemoteAiModelCatalog","createImplementation","AiModelCatalogAbstraction","HttpClient","Logger"],"mappings":";;;;;;AAQA,MAAMA,mBAAmB;AACzB,MAAMC,iBAAiB;AACvB,MAAMC,iBAAiB;AAEvB,MAAMC,eAAeC,EAAE,MAAM,CAAC,IAAI,GAAG,QAAQ;AAE7C,MAAMC,gBAAgBD,EAAE,MAAM,CAAC;IAC3B,WAAWA,EAAE,KAAK,CACdA,EAAE,MAAM,CAAC;QACL,IAAIA,EAAE,MAAM;QACZ,MAAMA,EAAE,MAAM;QACd,QAAQA,EAAE,KAAK,CACXA,EAAE,MAAM,CAAC;YACL,IAAIA,EAAE,MAAM;YACZ,MAAMA,EAAE,MAAM;YACd,YAAYD;YACZ,WAAWA;QACf;IAER;AAER;AAWA,IAAIG;AAEJ,MAAMC;IACF,YACqBC,UAAgC,EAChCC,MAAwB,CAC3C;aAFmBD,UAAU,GAAVA;aACAC,MAAM,GAANA;IAClB;IAEH,gBAA0D;QACtD,IAAIH,UAAUA,OAAO,SAAS,GAAGI,KAAK,GAAG,IACrC,OAAOJ,OAAO,SAAS;QAG3B,MAAMK,QAAuB;YACzB,WAAWD,KAAK,GAAG,KAAKT;YACxB,WAAW,IAAI,CAAC,cAAc,GAAG,IAAI,CAACW,CAAAA;gBAClC,IAAI,CAACA,WACDD,MAAM,SAAS,GAAGD,KAAK,GAAG,KAAKR;gBAEnC,OAAOU;YACX;QACJ;QACAN,SAASK;QAET,OAAOA,MAAM,SAAS;IAC1B;IAEA,MAAc,iBAA2D;QACrE,MAAME,MAAMC,aAAa;QAEzB,MAAMC,WAAW,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAEF;YAAK,WAAWb;QAAiB;QACtF,IAAIe,SAAS,MAAM,IAAI,YACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAEF;YAAK,OAAOE,SAAS,KAAK;QAAC,GAAG;QAIrD,MAAMC,SAASX,cAAc,SAAS,CAACU,SAAS,KAAK;QACrD,IAAI,CAACC,OAAO,OAAO,EAAE,YACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAEH;YAAK,OAAOG,OAAO,KAAK;QAAC,GAAG;QAInD,OAAOA,OAAO,IAAI,CAAC,SAAS;IAChC;AACJ;AAEO,MAAMC,uBAAuBC,qBAAqB;IACrD,aAAaC;IACb,gBAAgBZ;IAChB,cAAc;QAACa;QAAYC;KAAO;AACtC"}
|
|
@@ -82,6 +82,20 @@ export declare const AiModelRegistry: import("@webiny/di").Abstraction<IAiModelR
|
|
|
82
82
|
export declare namespace AiModelRegistry {
|
|
83
83
|
type Interface = IAiModelRegistry;
|
|
84
84
|
}
|
|
85
|
+
export interface AiCatalogProvider {
|
|
86
|
+
id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
models: IAiSdkModel[];
|
|
89
|
+
}
|
|
90
|
+
export interface IAiModelCatalog {
|
|
91
|
+
/** Returns `undefined` when the catalog can't be loaded. */
|
|
92
|
+
listProviders(): Promise<AiCatalogProvider[] | undefined>;
|
|
93
|
+
}
|
|
94
|
+
/** The published list of providers and models Webiny supports. SDK factories still gate what is offered. */
|
|
95
|
+
export declare const AiModelCatalog: import("@webiny/di").Abstraction<IAiModelCatalog>;
|
|
96
|
+
export declare namespace AiModelCatalog {
|
|
97
|
+
type Interface = IAiModelCatalog;
|
|
98
|
+
}
|
|
85
99
|
export interface IAiSdkTool<TInput = any> {
|
|
86
100
|
readonly name: string;
|
|
87
101
|
readonly description: string;
|
|
@@ -4,8 +4,9 @@ const AiSdkFactory = createAbstraction("AiSdkFactory");
|
|
|
4
4
|
const AiConnectionFactory = createAbstraction("AiConnectionFactory");
|
|
5
5
|
const Ai = createAbstraction("Ai");
|
|
6
6
|
const AiModelRegistry = createAbstraction("AiModelRegistry");
|
|
7
|
+
const AiModelCatalog = createAbstraction("AiModelCatalog");
|
|
7
8
|
const AiSdkTool = createAbstraction("AiSdkTool");
|
|
8
9
|
const AiSdkTools = createAbstraction("AiSdkTools");
|
|
9
|
-
export { Ai, AiConnectionFactory, AiModelRegistry, AiSdk, AiSdkFactory, AiSdkTool, AiSdkTools };
|
|
10
|
+
export { Ai, AiConnectionFactory, AiModelCatalog, AiModelRegistry, AiSdk, AiSdkFactory, AiSdkTool, AiSdkTools };
|
|
10
11
|
|
|
11
12
|
//# sourceMappingURL=abstractions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/ai/abstractions.js","sources":["../../../src/features/ai/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\nimport type { generateText } from \"ai\";\nimport type { streamText } from \"ai\";\nimport type { LanguageModel } from \"ai\";\nimport type { FlexibleSchema, ToolSet } from \"ai\";\n\n// AiSdk\n\nexport interface IAiSdkModel {\n id: string; // raw model id, e.g. \"claude-sonnet-4-5\"\n name: string; // human-friendly name, e.g. \"Claude Sonnet 4.5\"\n deprecated?: Date;\n endOfLife?: Date;\n}\n\nexport interface IAiSdk {\n languageModel(modelId: string): LanguageModel;\n}\n\n/** A single AI SDK instance (e.g. OpenAI, Anthropic) that resolves model instances. */\nexport const AiSdk = createAbstraction<IAiSdk>(\"AiSdk\");\n\nexport namespace AiSdk {\n export type Interface = IAiSdk;\n}\n\n// AiSdkFactory\n\nexport interface IAiSdkFactory {\n readonly id: string; // machine id, e.g. \"anthropic\"\n readonly name: string; // human-friendly name, e.g. \"Anthropic\"\n readonly models: readonly IAiSdkModel[]; // static model list, no API call needed\n execute(apiKey?: string): Promise<IAiSdk>;\n}\n\n/** Factory that asynchronously initialises an AI SDK. Register one per provider namespace. */\nexport const AiSdkFactory = createAbstraction<IAiSdkFactory>(\"AiSdkFactory\");\n\nexport namespace AiSdkFactory {\n export type Interface = IAiSdkFactory;\n}\n\n// AiConnection\n\nexport interface IAiConnectionInline {\n readonly sdkName: string;\n readonly apiKey?: string;\n}\n\nexport interface IAiConnection extends IAiConnectionInline {\n readonly id: string;\n}\n\n// AiConnectionFactory\n\nexport interface IAiConnectionFactory {\n execute(): Promise<IAiConnection>;\n}\n\n/** Factory that asynchronously produces an AiConnection. */\nexport const AiConnectionFactory = createAbstraction<IAiConnectionFactory>(\"AiConnectionFactory\");\n\nexport namespace AiConnectionFactory {\n export type Interface = IAiConnectionFactory;\n}\n\n// Ai\n\ntype SDKGenerateTextParams = Parameters<typeof generateText>[0];\ntype SDKStreamTextParams = Parameters<typeof streamText>[0];\n\nexport type AiGenerateTextParams = Omit<SDKGenerateTextParams, \"model\"> & {\n model: string;\n connection?: string | IAiConnectionInline;\n};\nexport type AiStreamTextParams = Omit<SDKStreamTextParams, \"model\"> & {\n model: string;\n connection?: string | IAiConnectionInline;\n};\n\nexport interface AiModel {\n providerId: string; // e.g. \"anthropic\"\n providerName: string; // e.g. \"Anthropic\"\n modelId: string; // e.g. \"claude-sonnet-4-5\"\n modelName: string; // e.g. \"Claude Sonnet 4.5\"\n deprecated: Date | undefined;\n endOfLife: Date | undefined;\n}\n\nexport interface IAi {\n generateText(params: AiGenerateTextParams): ReturnType<typeof generateText>;\n streamText(params: AiStreamTextParams): Promise<ReturnType<typeof streamText>>;\n listModels(): Promise<AiModel[]>;\n listModelsByConnections(): Promise<AiModel[]>;\n listModelsByConnection(connection: string | IAiConnectionInline): Promise<AiModel[]>;\n}\n\n/** Interact with AI language models using registered providers. */\nexport const Ai = createAbstraction<IAi>(\"Ai\");\n\nexport namespace Ai {\n export type Interface = IAi;\n export type GenerateTextParams = AiGenerateTextParams;\n export type StreamTextParams = AiStreamTextParams;\n}\n\n// AiModelRegistry\n\nexport interface IAiModelRegistry {\n listModels(): Promise<AiModel[]>;\n}\n\n/** Single source of truth for available AI models. Decoratable to restrict the model list. */\nexport const AiModelRegistry = createAbstraction<IAiModelRegistry>(\"AiModelRegistry\");\n\nexport namespace AiModelRegistry {\n export type Interface = IAiModelRegistry;\n}\n\n// AiSdkTool\n\nexport interface IAiSdkTool<TInput = any> {\n readonly name: string;\n readonly description: string;\n readonly inputSchema: FlexibleSchema<TInput>;\n execute(input: TInput): Promise<unknown>;\n}\n\n/** A single tool that can be provided to AI generateText/streamText calls. */\nexport const AiSdkTool = createAbstraction<IAiSdkTool>(\"AiSdkTool\");\n\nexport namespace AiSdkTool {\n export type Interface = IAiSdkTool;\n}\n\n// AiSdkTools\n\nexport interface IAiSdkTools {\n getToolSet(): ToolSet;\n}\n\n/** Collection of AI SDK tools. Returns a ready-to-use ToolSet for generateText/streamText. */\nexport const AiSdkTools = createAbstraction<IAiSdkTools>(\"AiSdkTools\");\n\nexport namespace AiSdkTools {\n export type Interface = IAiSdkTools;\n}\n"],"names":["AiSdk","createAbstraction","AiSdkFactory","AiConnectionFactory","Ai","AiModelRegistry","AiSdkTool","AiSdkTools"],"mappings":";AAoBO,MAAMA,QAAQC,kBAA0B;AAgBxC,MAAMC,eAAeD,kBAAiC;AAwBtD,MAAME,sBAAsBF,kBAAwC;AAsCpE,MAAMG,KAAKH,kBAAuB;AAelC,MAAMI,kBAAkBJ,kBAAoC;
|
|
1
|
+
{"version":3,"file":"features/ai/abstractions.js","sources":["../../../src/features/ai/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\nimport type { generateText } from \"ai\";\nimport type { streamText } from \"ai\";\nimport type { LanguageModel } from \"ai\";\nimport type { FlexibleSchema, ToolSet } from \"ai\";\n\n// AiSdk\n\nexport interface IAiSdkModel {\n id: string; // raw model id, e.g. \"claude-sonnet-4-5\"\n name: string; // human-friendly name, e.g. \"Claude Sonnet 4.5\"\n deprecated?: Date;\n endOfLife?: Date;\n}\n\nexport interface IAiSdk {\n languageModel(modelId: string): LanguageModel;\n}\n\n/** A single AI SDK instance (e.g. OpenAI, Anthropic) that resolves model instances. */\nexport const AiSdk = createAbstraction<IAiSdk>(\"AiSdk\");\n\nexport namespace AiSdk {\n export type Interface = IAiSdk;\n}\n\n// AiSdkFactory\n\nexport interface IAiSdkFactory {\n readonly id: string; // machine id, e.g. \"anthropic\"\n readonly name: string; // human-friendly name, e.g. \"Anthropic\"\n readonly models: readonly IAiSdkModel[]; // static model list, no API call needed\n execute(apiKey?: string): Promise<IAiSdk>;\n}\n\n/** Factory that asynchronously initialises an AI SDK. Register one per provider namespace. */\nexport const AiSdkFactory = createAbstraction<IAiSdkFactory>(\"AiSdkFactory\");\n\nexport namespace AiSdkFactory {\n export type Interface = IAiSdkFactory;\n}\n\n// AiConnection\n\nexport interface IAiConnectionInline {\n readonly sdkName: string;\n readonly apiKey?: string;\n}\n\nexport interface IAiConnection extends IAiConnectionInline {\n readonly id: string;\n}\n\n// AiConnectionFactory\n\nexport interface IAiConnectionFactory {\n execute(): Promise<IAiConnection>;\n}\n\n/** Factory that asynchronously produces an AiConnection. */\nexport const AiConnectionFactory = createAbstraction<IAiConnectionFactory>(\"AiConnectionFactory\");\n\nexport namespace AiConnectionFactory {\n export type Interface = IAiConnectionFactory;\n}\n\n// Ai\n\ntype SDKGenerateTextParams = Parameters<typeof generateText>[0];\ntype SDKStreamTextParams = Parameters<typeof streamText>[0];\n\nexport type AiGenerateTextParams = Omit<SDKGenerateTextParams, \"model\"> & {\n model: string;\n connection?: string | IAiConnectionInline;\n};\nexport type AiStreamTextParams = Omit<SDKStreamTextParams, \"model\"> & {\n model: string;\n connection?: string | IAiConnectionInline;\n};\n\nexport interface AiModel {\n providerId: string; // e.g. \"anthropic\"\n providerName: string; // e.g. \"Anthropic\"\n modelId: string; // e.g. \"claude-sonnet-4-5\"\n modelName: string; // e.g. \"Claude Sonnet 4.5\"\n deprecated: Date | undefined;\n endOfLife: Date | undefined;\n}\n\nexport interface IAi {\n generateText(params: AiGenerateTextParams): ReturnType<typeof generateText>;\n streamText(params: AiStreamTextParams): Promise<ReturnType<typeof streamText>>;\n listModels(): Promise<AiModel[]>;\n listModelsByConnections(): Promise<AiModel[]>;\n listModelsByConnection(connection: string | IAiConnectionInline): Promise<AiModel[]>;\n}\n\n/** Interact with AI language models using registered providers. */\nexport const Ai = createAbstraction<IAi>(\"Ai\");\n\nexport namespace Ai {\n export type Interface = IAi;\n export type GenerateTextParams = AiGenerateTextParams;\n export type StreamTextParams = AiStreamTextParams;\n}\n\n// AiModelRegistry\n\nexport interface IAiModelRegistry {\n listModels(): Promise<AiModel[]>;\n}\n\n/** Single source of truth for available AI models. Decoratable to restrict the model list. */\nexport const AiModelRegistry = createAbstraction<IAiModelRegistry>(\"AiModelRegistry\");\n\nexport namespace AiModelRegistry {\n export type Interface = IAiModelRegistry;\n}\n\n// AiModelCatalog\n\nexport interface AiCatalogProvider {\n id: string; // Matches `AiSdkFactory.id`, e.g. \"anthropic\".\n name: string; // Human-friendly name, e.g. \"Anthropic\".\n models: IAiSdkModel[];\n}\n\nexport interface IAiModelCatalog {\n /** Returns `undefined` when the catalog can't be loaded. */\n listProviders(): Promise<AiCatalogProvider[] | undefined>;\n}\n\n/** The published list of providers and models Webiny supports. SDK factories still gate what is offered. */\nexport const AiModelCatalog = createAbstraction<IAiModelCatalog>(\"AiModelCatalog\");\n\nexport namespace AiModelCatalog {\n export type Interface = IAiModelCatalog;\n}\n\n// AiSdkTool\n\nexport interface IAiSdkTool<TInput = any> {\n readonly name: string;\n readonly description: string;\n readonly inputSchema: FlexibleSchema<TInput>;\n execute(input: TInput): Promise<unknown>;\n}\n\n/** A single tool that can be provided to AI generateText/streamText calls. */\nexport const AiSdkTool = createAbstraction<IAiSdkTool>(\"AiSdkTool\");\n\nexport namespace AiSdkTool {\n export type Interface = IAiSdkTool;\n}\n\n// AiSdkTools\n\nexport interface IAiSdkTools {\n getToolSet(): ToolSet;\n}\n\n/** Collection of AI SDK tools. Returns a ready-to-use ToolSet for generateText/streamText. */\nexport const AiSdkTools = createAbstraction<IAiSdkTools>(\"AiSdkTools\");\n\nexport namespace AiSdkTools {\n export type Interface = IAiSdkTools;\n}\n"],"names":["AiSdk","createAbstraction","AiSdkFactory","AiConnectionFactory","Ai","AiModelRegistry","AiModelCatalog","AiSdkTool","AiSdkTools"],"mappings":";AAoBO,MAAMA,QAAQC,kBAA0B;AAgBxC,MAAMC,eAAeD,kBAAiC;AAwBtD,MAAME,sBAAsBF,kBAAwC;AAsCpE,MAAMG,KAAKH,kBAAuB;AAelC,MAAMI,kBAAkBJ,kBAAoC;AAoB5D,MAAMK,iBAAiBL,kBAAmC;AAgB1D,MAAMM,YAAYN,kBAA8B;AAahD,MAAMO,aAAaP,kBAA+B"}
|
package/features/ai/feature.js
CHANGED
|
@@ -4,6 +4,7 @@ import { AnthropicSdkFactory } from "./AnthropicSdkFactory.js";
|
|
|
4
4
|
import { GoogleSdkFactory } from "./GoogleSdkFactory.js";
|
|
5
5
|
import { Ai } from "./Ai.js";
|
|
6
6
|
import { AiModelRegistry } from "./AiModelRegistry.js";
|
|
7
|
+
import { RemoteAiModelCatalog } from "./RemoteAiModelCatalog.js";
|
|
7
8
|
import { AiSdkTools } from "./AiSdkTools.js";
|
|
8
9
|
import { AiOutputToolRegistry } from "./toolPipeline/AiOutputToolRegistry.js";
|
|
9
10
|
import { AiToolPipelineRunner } from "./toolPipeline/AiToolPipelineRunner.js";
|
|
@@ -15,6 +16,7 @@ const AiFeature = createFeature({
|
|
|
15
16
|
container.register(AnthropicSdkFactory);
|
|
16
17
|
container.register(GoogleSdkFactory);
|
|
17
18
|
container.register(Ai);
|
|
19
|
+
container.register(RemoteAiModelCatalog);
|
|
18
20
|
container.register(AiModelRegistry);
|
|
19
21
|
container.register(AiSdkTools);
|
|
20
22
|
container.register(AiOutputToolRegistry);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/ai/feature.js","sources":["../../../src/features/ai/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { OpenAiSdkFactory } from \"./OpenAiSdkFactory.js\";\nimport { AnthropicSdkFactory } from \"./AnthropicSdkFactory.js\";\nimport { GoogleSdkFactory } from \"./GoogleSdkFactory.js\";\nimport { Ai } from \"./Ai.js\";\nimport { AiModelRegistry } from \"./AiModelRegistry.js\";\nimport { AiSdkTools } from \"./AiSdkTools.js\";\nimport { AiOutputToolRegistry } from \"./toolPipeline/AiOutputToolRegistry.js\";\nimport { AiToolPipelineRunner } from \"./toolPipeline/AiToolPipelineRunner.js\";\nimport { DefaultTextExtractor } from \"./TextExtractor/DefaultTextExtractor.js\";\n\nexport const AiFeature = createFeature({\n name: \"AiFeature\",\n register(container) {\n container.register(OpenAiSdkFactory);\n container.register(AnthropicSdkFactory);\n container.register(GoogleSdkFactory);\n container.register(Ai);\n container.register(AiModelRegistry);\n container.register(AiSdkTools);\n container.register(AiOutputToolRegistry);\n container.register(AiToolPipelineRunner);\n container.register(DefaultTextExtractor);\n }\n});\n"],"names":["AiFeature","createFeature","container","OpenAiSdkFactory","AnthropicSdkFactory","GoogleSdkFactory","Ai","AiModelRegistry","AiSdkTools","AiOutputToolRegistry","AiToolPipelineRunner","DefaultTextExtractor"],"mappings":"
|
|
1
|
+
{"version":3,"file":"features/ai/feature.js","sources":["../../../src/features/ai/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { OpenAiSdkFactory } from \"./OpenAiSdkFactory.js\";\nimport { AnthropicSdkFactory } from \"./AnthropicSdkFactory.js\";\nimport { GoogleSdkFactory } from \"./GoogleSdkFactory.js\";\nimport { Ai } from \"./Ai.js\";\nimport { AiModelRegistry } from \"./AiModelRegistry.js\";\nimport { RemoteAiModelCatalog } from \"./RemoteAiModelCatalog.js\";\nimport { AiSdkTools } from \"./AiSdkTools.js\";\nimport { AiOutputToolRegistry } from \"./toolPipeline/AiOutputToolRegistry.js\";\nimport { AiToolPipelineRunner } from \"./toolPipeline/AiToolPipelineRunner.js\";\nimport { DefaultTextExtractor } from \"./TextExtractor/DefaultTextExtractor.js\";\n\nexport const AiFeature = createFeature({\n name: \"AiFeature\",\n register(container) {\n container.register(OpenAiSdkFactory);\n container.register(AnthropicSdkFactory);\n container.register(GoogleSdkFactory);\n container.register(Ai);\n container.register(RemoteAiModelCatalog);\n container.register(AiModelRegistry);\n container.register(AiSdkTools);\n container.register(AiOutputToolRegistry);\n container.register(AiToolPipelineRunner);\n container.register(DefaultTextExtractor);\n }\n});\n"],"names":["AiFeature","createFeature","container","OpenAiSdkFactory","AnthropicSdkFactory","GoogleSdkFactory","Ai","RemoteAiModelCatalog","AiModelRegistry","AiSdkTools","AiOutputToolRegistry","AiToolPipelineRunner","DefaultTextExtractor"],"mappings":";;;;;;;;;;;AAYO,MAAMA,YAAYC,cAAc;IACnC,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;QACnBD,UAAU,QAAQ,CAACE;QACnBF,UAAU,QAAQ,CAACG;QACnBH,UAAU,QAAQ,CAACI;QACnBJ,UAAU,QAAQ,CAACK;QACnBL,UAAU,QAAQ,CAACM;QACnBN,UAAU,QAAQ,CAACO;QACnBP,UAAU,QAAQ,CAACQ;QACnBR,UAAU,QAAQ,CAACS;QACnBT,UAAU,QAAQ,CAACU;IACvB;AACJ"}
|
package/features/ai/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { AiSdk, AiSdkFactory, AiConnectionFactory, Ai, AiModelRegistry, AiSdkTool, AiSdkTools } from "./abstractions.js";
|
|
2
|
-
export type { IAiConnection, IAiConnectionInline, AiModel, IAiSdkModel, IAiModelRegistry, IAiSdkTool, IAiSdkTools } from "./abstractions.js";
|
|
1
|
+
export { AiSdk, AiSdkFactory, AiConnectionFactory, Ai, AiModelRegistry, AiModelCatalog, AiSdkTool, AiSdkTools } from "./abstractions.js";
|
|
2
|
+
export type { IAiConnection, IAiConnectionInline, AiModel, IAiSdkModel, IAiModelRegistry, AiCatalogProvider, IAiSdkTool, IAiSdkTools } from "./abstractions.js";
|
|
3
3
|
export { AiOutputTool, AiOutputToolRegistry, AiToolPipelineRunner } from "./toolPipeline/index.js";
|
|
4
4
|
export type { IAiOutputTool, IAiOutputToolRegistry, IAiToolPipelineRunner } from "./toolPipeline/index.js";
|
|
5
5
|
export { TextExtractor, DefaultTextExtractor } from "./TextExtractor/index.js";
|
package/features/ai/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Ai, AiConnectionFactory, AiModelRegistry, AiSdk, AiSdkFactory, AiSdkTool, AiSdkTools } from "./abstractions.js";
|
|
1
|
+
export { Ai, AiConnectionFactory, AiModelCatalog, AiModelRegistry, AiSdk, AiSdkFactory, AiSdkTool, AiSdkTools } from "./abstractions.js";
|
|
2
2
|
export { AiOutputTool, AiOutputToolRegistry, AiToolPipelineRunner } from "./toolPipeline/index.js";
|
|
3
3
|
export { DefaultTextExtractor, TextExtractor } from "./TextExtractor/index.js";
|
|
4
4
|
export { AiAfterGenerateTextEvent, AiAfterGenerateTextEventHandler, AiBeforeGenerateTextEvent, AiBeforeGenerateTextEventHandler, AiBeforeStreamTextEvent, AiBeforeStreamTextEventHandler, AiGenerateTextErrorEvent, AiGenerateTextErrorEventHandler } from "./events.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/debugger/__tests__/serialize.test.js","sources":["../../../../src/features/debugger/__tests__/serialize.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { serialize } from \"../serialize.js\";\nimport { DEFAULT_LIMITS, type IDebuggerLimits } from \"../limits.js\";\n\nconst limits = (overrides: Partial<IDebuggerLimits> = {}): IDebuggerLimits => {\n return { ...DEFAULT_LIMITS, ...overrides };\n};\n\nconst parse = (value: unknown, overrides: Partial<IDebuggerLimits> = {}) => {\n const result = serialize(value, limits(overrides));\n return { ...result, parsed: JSON.parse(result.json) };\n};\n\ndescribe(\"serialize\", () => {\n it(\"should always produce parseable JSON\", () => {\n const circular: Record<string, unknown> = { name: \"root\" };\n circular.self = circular;\n\n const { parsed } = parse(circular);\n\n expect(parsed).toEqual({ name: \"root\", self: \"#[Circular]\" });\n });\n\n it(\"should not report siblings as circular\", () => {\n const shared = { id: 1 };\n\n const { parsed } = parse({ a: shared, b: shared });\n\n expect(parsed).toEqual({ a: { id: 1 }, b: { id: 1 } });\n });\n\n it(\"should serialize an Error, which JSON.stringify renders as an empty object\", () => {\n const error = new Error(\"Boom\");\n\n const { parsed } = parse({ error });\n\n expect(parsed.error.name).toBe(\"Error\");\n expect(parsed.error.message).toBe(\"Boom\");\n expect(typeof parsed.error.stack).toBe(\"string\");\n });\n\n it(\"should serialize a BigInt, which JSON.stringify throws on\", () => {\n const { parsed } = parse({ value: BigInt(123) });\n\n expect(parsed).toEqual({ value: \"123n\" });\n });\n\n it(\"should describe binary data without including its contents\", () => {\n const { parsed } = parse({ buffer: Buffer.from(\"hello world\") });\n\n expect(parsed).toEqual({ buffer: \"#[Buffer 11 bytes]\" });\n });\n\n it(\"should serialize Map and Set\", () => {\n const { parsed } = parse({\n map: new Map([[\"a\", 1]]),\n set: new Set([1, 2])\n });\n\n expect(parsed.map).toEqual({ __type: \"Map\", entries: [[\"a\", 1]] });\n expect(parsed.set).toEqual({ __type: \"Set\", items: [1, 2] });\n });\n\n it(\"should drop an undefined property, so a logged payload matches what was sent\", () => {\n const { parsed } = parse({ kept: 1, dropped: undefined });\n\n expect(parsed).toEqual({ kept: 1 });\n });\n\n it(\"should keep undefined array elements, where dropping would shift every later index\", () => {\n const { parsed } = parse({ items: [1, undefined, 3] });\n\n expect(parsed).toEqual({ items: [1, \"#[Undefined value]\", 3] });\n });\n\n it(\"should not throw when a getter throws\", () => {\n const value = {\n get exploding() {\n throw new Error(\"Nope\");\n },\n safe: 1\n };\n\n const { parsed } = parse(value);\n\n expect(parsed.exploding).toBe(\"#[Throws: Nope]\");\n expect(parsed.safe).toBe(1);\n });\n\n it(\"should cap depth\", () => {\n const { parsed } = parse({ a: { b: { c: { d: \"deep\" } } } }, { depth: 2 });\n\n expect(parsed).toEqual({ a: { b: \"#[Depth limit]\" } });\n });\n\n it(\"should cap string length and say how much was cut\", () => {\n const { parsed } = parse({ text: \"x\".repeat(20) }, { stringLength: 5 });\n\n expect(parsed).toEqual({ text: \"xxxxx…(15 more)\" });\n });\n\n it(\"should cap array length and say how much was cut\", () => {\n const { parsed } = parse({ items: [1, 2, 3, 4, 5] }, { arrayLength: 2 });\n\n expect(parsed).toEqual({ items: [1, 2, \"…(3 more items)\"] });\n });\n\n it(\"should replace the payload wholesale when it exceeds the per-entry cap\", () => {\n const result = parse({ text: \"x\".repeat(5000) }, { entryBytes: 100, stringLength: 100000 });\n\n expect(result.truncated).toBe(true);\n expect(result.parsed.truncated).toBe(true);\n expect(result.parsed.originalBytes).toBeGreaterThan(100);\n expect(result.bytes).toBeLessThanOrEqual(100);\n });\n\n it(\"should never invoke a custom toJSON, so it cannot be broken by one\", () => {\n const value = {\n id: 1,\n toJSON() {\n throw new Error(\"Nope\");\n }\n };\n\n const { parsed } = parse({ value });\n\n expect(parsed.value.id).toBe(1);\n expect(parsed.value.toJSON).toBe(\"#[Function: toJSON]\");\n });\n\n it(\"should keep non-finite numbers readable instead of turning them into null\", () => {\n const { parsed } = parse({ a: Infinity, b: NaN });\n\n expect(parsed).toEqual({ a: \"Infinity\", b: \"NaN\" });\n });\n});\n"],"names":["limits","overrides","DEFAULT_LIMITS","parse","value","result","serialize","JSON","describe","it","circular","parsed","expect","shared","error","Error","BigInt","Buffer","Map","Set","undefined","Infinity","NaN"],"mappings":";;;AAIA,MAAMA,SAAS,CAACC,YAAsC,CAAC,CAAC,GAC7C;QAAE,GAAGC,cAAc;QAAE,GAAGD,SAAS;IAAC;AAG7C,MAAME,QAAQ,CAACC,OAAgBH,YAAsC,CAAC,CAAC;IACnE,MAAMI,SAASC,UAAUF,OAAOJ,OAAOC;IACvC,OAAO;QAAE,GAAGI,MAAM;QAAE,QAAQE,KAAK,KAAK,CAACF,OAAO,IAAI;IAAE;AACxD;AAEAG,SAAS,aAAa;IAClBC,GAAG,wCAAwC;QACvC,MAAMC,WAAoC;YAAE,MAAM;QAAO;QACzDA,SAAS,IAAI,GAAGA;QAEhB,MAAM,EAAEC,MAAM,EAAE,GAAGR,MAAMO;QAEzBE,OAAOD,QAAQ,OAAO,CAAC;YAAE,MAAM;YAAQ,MAAM;QAAc;IAC/D;IAEAF,GAAG,0CAA0C;QACzC,MAAMI,SAAS;YAAE,IAAI;QAAE;QAEvB,MAAM,EAAEF,MAAM,EAAE,GAAGR,MAAM;YAAE,GAAGU;YAAQ,GAAGA;QAAO;QAEhDD,OAAOD,QAAQ,OAAO,CAAC;YAAE,GAAG;gBAAE,IAAI;YAAE;YAAG,GAAG;gBAAE,IAAI;YAAE;QAAE;IACxD;IAEAF,GAAG,8EAA8E;QAC7E,MAAMK,QAAQ,IAAIC,MAAM;QAExB,MAAM,EAAEJ,MAAM,EAAE,GAAGR,MAAM;YAAEW;QAAM;QAEjCF,OAAOD,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC;QAC/BC,OAAOD,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC;QAClCC,OAAO,OAAOD,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;IAC3C;IAEAF,GAAG,6DAA6D;QAC5D,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,OAAOa,OAAO;QAAK;QAE9CJ,OAAOD,QAAQ,OAAO,CAAC;YAAE,OAAO;QAAO;IAC3C;IAEAF,GAAG,8DAA8D;QAC7D,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,QAAQc,OAAO,IAAI,CAAC;QAAe;QAE9DL,OAAOD,QAAQ,OAAO,CAAC;YAAE,QAAQ;QAAqB;IAC1D;IAEAF,GAAG,gCAAgC;QAC/B,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YACrB,KAAK,IAAIe,IAAI;gBAAC;oBAAC;oBAAK;iBAAE;aAAC;YACvB,KAAK,IAAIC,IAAI;gBAAC;gBAAG;aAAE;QACvB;QAEAP,OAAOD,OAAO,GAAG,EAAE,OAAO,CAAC;YAAE,QAAQ;YAAO,SAAS;gBAAC;oBAAC;oBAAK;iBAAE;aAAC;QAAC;QAChEC,OAAOD,OAAO,GAAG,EAAE,OAAO,CAAC;YAAE,QAAQ;YAAO,OAAO;gBAAC;gBAAG;aAAE;QAAC;IAC9D;IAEAF,GAAG,gFAAgF;QAC/E,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,MAAM;YAAG,SAASiB;QAAU;QAEvDR,OAAOD,QAAQ,OAAO,CAAC;YAAE,MAAM;QAAE;IACrC;IAEAF,GAAG,sFAAsF;QACrF,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,OAAO;gBAAC;gBAAGiB;gBAAW;aAAE;QAAC;QAEpDR,OAAOD,QAAQ,OAAO,CAAC;YAAE,OAAO;gBAAC;gBAAG;gBAAsB;aAAE;QAAC;IACjE;IAEAF,GAAG,yCAAyC;QACxC,MAAML,QAAQ;YACV,IAAI
|
|
1
|
+
{"version":3,"file":"features/debugger/__tests__/serialize.test.js","sources":["../../../../src/features/debugger/__tests__/serialize.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { serialize } from \"../serialize.js\";\nimport { DEFAULT_LIMITS, type IDebuggerLimits } from \"../limits.js\";\n\nconst limits = (overrides: Partial<IDebuggerLimits> = {}): IDebuggerLimits => {\n return { ...DEFAULT_LIMITS, ...overrides };\n};\n\nconst parse = (value: unknown, overrides: Partial<IDebuggerLimits> = {}) => {\n const result = serialize(value, limits(overrides));\n return { ...result, parsed: JSON.parse(result.json) };\n};\n\ndescribe(\"serialize\", () => {\n it(\"should always produce parseable JSON\", () => {\n const circular: Record<string, unknown> = { name: \"root\" };\n circular.self = circular;\n\n const { parsed } = parse(circular);\n\n expect(parsed).toEqual({ name: \"root\", self: \"#[Circular]\" });\n });\n\n it(\"should not report siblings as circular\", () => {\n const shared = { id: 1 };\n\n const { parsed } = parse({ a: shared, b: shared });\n\n expect(parsed).toEqual({ a: { id: 1 }, b: { id: 1 } });\n });\n\n it(\"should serialize an Error, which JSON.stringify renders as an empty object\", () => {\n const error = new Error(\"Boom\");\n\n const { parsed } = parse({ error });\n\n expect(parsed.error.name).toBe(\"Error\");\n expect(parsed.error.message).toBe(\"Boom\");\n expect(typeof parsed.error.stack).toBe(\"string\");\n });\n\n it(\"should serialize a BigInt, which JSON.stringify throws on\", () => {\n const { parsed } = parse({ value: BigInt(123) });\n\n expect(parsed).toEqual({ value: \"123n\" });\n });\n\n it(\"should describe binary data without including its contents\", () => {\n const { parsed } = parse({ buffer: Buffer.from(\"hello world\") });\n\n expect(parsed).toEqual({ buffer: \"#[Buffer 11 bytes]\" });\n });\n\n it(\"should serialize Map and Set\", () => {\n const { parsed } = parse({\n map: new Map([[\"a\", 1]]),\n set: new Set([1, 2])\n });\n\n expect(parsed.map).toEqual({ __type: \"Map\", entries: [[\"a\", 1]] });\n expect(parsed.set).toEqual({ __type: \"Set\", items: [1, 2] });\n });\n\n it(\"should drop an undefined property, so a logged payload matches what was sent\", () => {\n const { parsed } = parse({ kept: 1, dropped: undefined });\n\n expect(parsed).toEqual({ kept: 1 });\n });\n\n it(\"should keep undefined array elements, where dropping would shift every later index\", () => {\n const { parsed } = parse({ items: [1, undefined, 3] });\n\n expect(parsed).toEqual({ items: [1, \"#[Undefined value]\", 3] });\n });\n\n it(\"should not throw when a getter throws\", () => {\n const value = {\n get exploding() {\n throw new Error(\"Nope\");\n },\n safe: 1\n };\n\n const { parsed } = parse(value);\n\n expect(parsed.exploding).toBe(\"#[Throws: Nope]\");\n expect(parsed.safe).toBe(1);\n });\n\n it(\"should cap depth\", () => {\n const { parsed } = parse({ a: { b: { c: { d: \"deep\" } } } }, { depth: 2 });\n\n expect(parsed).toEqual({ a: { b: \"#[Depth limit]\" } });\n });\n\n it(\"should cap string length and say how much was cut\", () => {\n const { parsed } = parse({ text: \"x\".repeat(20) }, { stringLength: 5 });\n\n expect(parsed).toEqual({ text: \"xxxxx…(15 more)\" });\n });\n\n it(\"should cap array length and say how much was cut\", () => {\n const { parsed } = parse({ items: [1, 2, 3, 4, 5] }, { arrayLength: 2 });\n\n expect(parsed).toEqual({ items: [1, 2, \"…(3 more items)\"] });\n });\n\n it(\"should replace the payload wholesale when it exceeds the per-entry cap\", () => {\n const result = parse({ text: \"x\".repeat(5000) }, { entryBytes: 100, stringLength: 100000 });\n\n expect(result.truncated).toBe(true);\n expect(result.parsed.truncated).toBe(true);\n expect(result.parsed.originalBytes).toBeGreaterThan(100);\n expect(result.bytes).toBeLessThanOrEqual(100);\n });\n\n it(\"should never invoke a custom toJSON, so it cannot be broken by one\", () => {\n const value = {\n id: 1,\n toJSON() {\n throw new Error(\"Nope\");\n }\n };\n\n const { parsed } = parse({ value });\n\n expect(parsed.value.id).toBe(1);\n expect(parsed.value.toJSON).toBe(\"#[Function: toJSON]\");\n });\n\n it(\"should keep non-finite numbers readable instead of turning them into null\", () => {\n const { parsed } = parse({ a: Infinity, b: NaN });\n\n expect(parsed).toEqual({ a: \"Infinity\", b: \"NaN\" });\n });\n});\n"],"names":["limits","overrides","DEFAULT_LIMITS","parse","value","result","serialize","JSON","describe","it","circular","parsed","expect","shared","error","Error","BigInt","Buffer","Map","Set","undefined","Infinity","NaN"],"mappings":";;;AAIA,MAAMA,SAAS,CAACC,YAAsC,CAAC,CAAC,GAC7C;QAAE,GAAGC,cAAc;QAAE,GAAGD,SAAS;IAAC;AAG7C,MAAME,QAAQ,CAACC,OAAgBH,YAAsC,CAAC,CAAC;IACnE,MAAMI,SAASC,UAAUF,OAAOJ,OAAOC;IACvC,OAAO;QAAE,GAAGI,MAAM;QAAE,QAAQE,KAAK,KAAK,CAACF,OAAO,IAAI;IAAE;AACxD;AAEAG,SAAS,aAAa;IAClBC,GAAG,wCAAwC;QACvC,MAAMC,WAAoC;YAAE,MAAM;QAAO;QACzDA,SAAS,IAAI,GAAGA;QAEhB,MAAM,EAAEC,MAAM,EAAE,GAAGR,MAAMO;QAEzBE,OAAOD,QAAQ,OAAO,CAAC;YAAE,MAAM;YAAQ,MAAM;QAAc;IAC/D;IAEAF,GAAG,0CAA0C;QACzC,MAAMI,SAAS;YAAE,IAAI;QAAE;QAEvB,MAAM,EAAEF,MAAM,EAAE,GAAGR,MAAM;YAAE,GAAGU;YAAQ,GAAGA;QAAO;QAEhDD,OAAOD,QAAQ,OAAO,CAAC;YAAE,GAAG;gBAAE,IAAI;YAAE;YAAG,GAAG;gBAAE,IAAI;YAAE;QAAE;IACxD;IAEAF,GAAG,8EAA8E;QAC7E,MAAMK,QAAQ,IAAIC,MAAM;QAExB,MAAM,EAAEJ,MAAM,EAAE,GAAGR,MAAM;YAAEW;QAAM;QAEjCF,OAAOD,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC;QAC/BC,OAAOD,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC;QAClCC,OAAO,OAAOD,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;IAC3C;IAEAF,GAAG,6DAA6D;QAC5D,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,OAAOa,OAAO;QAAK;QAE9CJ,OAAOD,QAAQ,OAAO,CAAC;YAAE,OAAO;QAAO;IAC3C;IAEAF,GAAG,8DAA8D;QAC7D,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,QAAQc,OAAO,IAAI,CAAC;QAAe;QAE9DL,OAAOD,QAAQ,OAAO,CAAC;YAAE,QAAQ;QAAqB;IAC1D;IAEAF,GAAG,gCAAgC;QAC/B,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YACrB,KAAK,IAAIe,IAAI;gBAAC;oBAAC;oBAAK;iBAAE;aAAC;YACvB,KAAK,IAAIC,IAAI;gBAAC;gBAAG;aAAE;QACvB;QAEAP,OAAOD,OAAO,GAAG,EAAE,OAAO,CAAC;YAAE,QAAQ;YAAO,SAAS;gBAAC;oBAAC;oBAAK;iBAAE;aAAC;QAAC;QAChEC,OAAOD,OAAO,GAAG,EAAE,OAAO,CAAC;YAAE,QAAQ;YAAO,OAAO;gBAAC;gBAAG;aAAE;QAAC;IAC9D;IAEAF,GAAG,gFAAgF;QAC/E,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,MAAM;YAAG,SAASiB;QAAU;QAEvDR,OAAOD,QAAQ,OAAO,CAAC;YAAE,MAAM;QAAE;IACrC;IAEAF,GAAG,sFAAsF;QACrF,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,OAAO;gBAAC;gBAAGiB;gBAAW;aAAE;QAAC;QAEpDR,OAAOD,QAAQ,OAAO,CAAC;YAAE,OAAO;gBAAC;gBAAG;gBAAsB;aAAE;QAAC;IACjE;IAEAF,GAAG,yCAAyC;QACxC,MAAML,QAAQ;YACV,IAAI;gBACA,MAAM,IAAIW,MAAM;YACpB;YACA,MAAM;QACV;QAEA,MAAM,EAAEJ,MAAM,EAAE,GAAGR,MAAMC;QAEzBQ,OAAOD,OAAO,SAAS,EAAE,IAAI,CAAC;QAC9BC,OAAOD,OAAO,IAAI,EAAE,IAAI,CAAC;IAC7B;IAEAF,GAAG,oBAAoB;QACnB,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,GAAG;gBAAE,GAAG;oBAAE,GAAG;wBAAE,GAAG;oBAAO;gBAAE;YAAE;QAAE,GAAG;YAAE,OAAO;QAAE;QAExES,OAAOD,QAAQ,OAAO,CAAC;YAAE,GAAG;gBAAE,GAAG;YAAiB;QAAE;IACxD;IAEAF,GAAG,qDAAqD;QACpD,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,MAAM,IAAI,MAAM,CAAC;QAAI,GAAG;YAAE,cAAc;QAAE;QAErES,OAAOD,QAAQ,OAAO,CAAC;YAAE,MAAM;QAAkB;IACrD;IAEAF,GAAG,oDAAoD;QACnD,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,OAAO;gBAAC;gBAAG;gBAAG;gBAAG;gBAAG;aAAE;QAAC,GAAG;YAAE,aAAa;QAAE;QAEtES,OAAOD,QAAQ,OAAO,CAAC;YAAE,OAAO;gBAAC;gBAAG;gBAAG;aAAkB;QAAC;IAC9D;IAEAF,GAAG,0EAA0E;QACzE,MAAMJ,SAASF,MAAM;YAAE,MAAM,IAAI,MAAM,CAAC;QAAM,GAAG;YAAE,YAAY;YAAK,cAAc;QAAO;QAEzFS,OAAOP,OAAO,SAAS,EAAE,IAAI,CAAC;QAC9BO,OAAOP,OAAO,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC;QACrCO,OAAOP,OAAO,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC;QACpDO,OAAOP,OAAO,KAAK,EAAE,mBAAmB,CAAC;IAC7C;IAEAI,GAAG,sEAAsE;QACrE,MAAML,QAAQ;YACV,IAAI;YACJ;gBACI,MAAM,IAAIW,MAAM;YACpB;QACJ;QAEA,MAAM,EAAEJ,MAAM,EAAE,GAAGR,MAAM;YAAEC;QAAM;QAEjCQ,OAAOD,OAAO,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC;QAC7BC,OAAOD,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC;IACrC;IAEAF,GAAG,6EAA6E;QAC5E,MAAM,EAAEE,MAAM,EAAE,GAAGR,MAAM;YAAE,GAAGkB,KAAQ;YAAE,GAAGC,KAAG;QAAC;QAE/CV,OAAOD,QAAQ,OAAO,CAAC;YAAE,GAAG;YAAY,GAAG;QAAM;IACrD;AACJ"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Result } from "@webiny/feature/api";
|
|
2
|
+
import { HttpClient as HttpClientAbstraction } from "./abstractions.js";
|
|
3
|
+
declare class HttpClientImpl implements HttpClientAbstraction.Interface {
|
|
4
|
+
requestJson(request: HttpClientAbstraction.Request): Promise<Result<unknown, HttpClientAbstraction.Error>>;
|
|
5
|
+
}
|
|
6
|
+
export declare const HttpClient: import("@webiny/di").Implementation<typeof HttpClientImpl>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Result, createImplementation } from "@webiny/feature/api";
|
|
2
|
+
import { HttpClient } from "./abstractions.js";
|
|
3
|
+
import { HttpRequestError } from "./HttpRequestError.js";
|
|
4
|
+
import { HttpStatusError } from "./HttpStatusError.js";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
6
|
+
function createRequestInit(request) {
|
|
7
|
+
const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
8
|
+
const headers = new Headers(request.headers);
|
|
9
|
+
const init = {
|
|
10
|
+
method: request.method ?? "GET",
|
|
11
|
+
headers,
|
|
12
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
13
|
+
};
|
|
14
|
+
if (void 0 !== request.body) {
|
|
15
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
16
|
+
init.body = JSON.stringify(request.body);
|
|
17
|
+
}
|
|
18
|
+
return init;
|
|
19
|
+
}
|
|
20
|
+
function isTimeout(error) {
|
|
21
|
+
return error instanceof Error && "TimeoutError" === error.name;
|
|
22
|
+
}
|
|
23
|
+
function parseJson(text) {
|
|
24
|
+
if ("" === text) return Result.ok(void 0);
|
|
25
|
+
try {
|
|
26
|
+
const value = JSON.parse(text);
|
|
27
|
+
return Result.ok(value);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return Result.fail(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class HttpClientImpl {
|
|
33
|
+
async requestJson(request) {
|
|
34
|
+
const { url } = request;
|
|
35
|
+
const init = createRequestInit(request);
|
|
36
|
+
let response;
|
|
37
|
+
let text;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetch(url, init);
|
|
40
|
+
text = await response.text();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const reason = isTimeout(error) ? "timeout" : "network";
|
|
43
|
+
return Result.fail(new HttpRequestError({
|
|
44
|
+
url,
|
|
45
|
+
reason
|
|
46
|
+
}, error));
|
|
47
|
+
}
|
|
48
|
+
const body = parseJson(text);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const statusError = new HttpStatusError({
|
|
51
|
+
url,
|
|
52
|
+
status: response.status,
|
|
53
|
+
statusText: response.statusText,
|
|
54
|
+
body: body.isOk() ? body.value : void 0
|
|
55
|
+
});
|
|
56
|
+
return Result.fail(statusError);
|
|
57
|
+
}
|
|
58
|
+
if (body.isFail()) return Result.fail(new HttpRequestError({
|
|
59
|
+
url,
|
|
60
|
+
reason: "invalidJson"
|
|
61
|
+
}, body.error));
|
|
62
|
+
return Result.ok(body.value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const HttpClient_HttpClient = createImplementation({
|
|
66
|
+
abstraction: HttpClient,
|
|
67
|
+
implementation: HttpClientImpl,
|
|
68
|
+
dependencies: []
|
|
69
|
+
});
|
|
70
|
+
export { HttpClient_HttpClient as HttpClient };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=HttpClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/httpClient/HttpClient.js","sources":["../../../src/features/httpClient/HttpClient.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/feature/api\";\nimport { Result } from \"@webiny/feature/api\";\nimport { HttpClient as HttpClientAbstraction } from \"./abstractions.js\";\nimport { HttpRequestError } from \"./HttpRequestError.js\";\nimport { HttpStatusError } from \"./HttpStatusError.js\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nfunction createRequestInit(request: HttpClientAbstraction.Request): RequestInit {\n const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const headers = new Headers(request.headers);\n const init: RequestInit = {\n method: request.method ?? \"GET\",\n headers,\n signal: AbortSignal.timeout(timeoutMs)\n };\n\n if (request.body !== undefined) {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n init.body = JSON.stringify(request.body);\n }\n\n return init;\n}\n\nfunction isTimeout(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n\n// An empty body parses to `undefined`, so a 204 is not an invalid response.\nfunction parseJson(text: string): Result<unknown, unknown> {\n if (text === \"\") {\n return Result.ok(undefined);\n }\n try {\n const value: unknown = JSON.parse(text);\n return Result.ok(value);\n } catch (error) {\n return Result.fail(error);\n }\n}\n\nclass HttpClientImpl implements HttpClientAbstraction.Interface {\n async requestJson(\n request: HttpClientAbstraction.Request\n ): Promise<Result<unknown, HttpClientAbstraction.Error>> {\n const { url } = request;\n const init = createRequestInit(request);\n\n let response: Response;\n let text: string;\n try {\n response = await fetch(url, init);\n text = await response.text();\n } catch (error) {\n const reason = isTimeout(error) ? \"timeout\" : \"network\";\n return Result.fail(new HttpRequestError({ url, reason }, error));\n }\n\n const body = parseJson(text);\n\n if (!response.ok) {\n const statusError = new HttpStatusError({\n url,\n status: response.status,\n statusText: response.statusText,\n body: body.isOk() ? body.value : undefined\n });\n return Result.fail(statusError);\n }\n\n if (body.isFail()) {\n return Result.fail(new HttpRequestError({ url, reason: \"invalidJson\" }, body.error));\n }\n\n return Result.ok(body.value);\n }\n}\n\nexport const HttpClient = createImplementation({\n abstraction: HttpClientAbstraction,\n implementation: HttpClientImpl,\n dependencies: []\n});\n"],"names":["DEFAULT_TIMEOUT_MS","createRequestInit","request","timeoutMs","headers","Headers","init","AbortSignal","undefined","JSON","isTimeout","error","Error","parseJson","text","Result","value","HttpClientImpl","url","response","fetch","reason","HttpRequestError","body","statusError","HttpStatusError","HttpClient","createImplementation","HttpClientAbstraction"],"mappings":";;;;AAMA,MAAMA,qBAAqB;AAE3B,SAASC,kBAAkBC,OAAsC;IAC7D,MAAMC,YAAYD,QAAQ,SAAS,IAAIF;IACvC,MAAMI,UAAU,IAAIC,QAAQH,QAAQ,OAAO;IAC3C,MAAMI,OAAoB;QACtB,QAAQJ,QAAQ,MAAM,IAAI;QAC1BE;QACA,QAAQG,YAAY,OAAO,CAACJ;IAChC;IAEA,IAAID,AAAiBM,WAAjBN,QAAQ,IAAI,EAAgB;QAC5B,IAAI,CAACE,QAAQ,GAAG,CAAC,iBACbA,QAAQ,GAAG,CAAC,gBAAgB;QAEhCE,KAAK,IAAI,GAAGG,KAAK,SAAS,CAACP,QAAQ,IAAI;IAC3C;IAEA,OAAOI;AACX;AAEA,SAASI,UAAUC,KAAc;IAC7B,OAAOA,iBAAiBC,SAASD,AAAe,mBAAfA,MAAM,IAAI;AAC/C;AAGA,SAASE,UAAUC,IAAY;IAC3B,IAAIA,AAAS,OAATA,MACA,OAAOC,OAAO,EAAE,CAACP;IAErB,IAAI;QACA,MAAMQ,QAAiBP,KAAK,KAAK,CAACK;QAClC,OAAOC,OAAO,EAAE,CAACC;IACrB,EAAE,OAAOL,OAAO;QACZ,OAAOI,OAAO,IAAI,CAACJ;IACvB;AACJ;AAEA,MAAMM;IACF,MAAM,YACFf,OAAsC,EACe;QACrD,MAAM,EAAEgB,GAAG,EAAE,GAAGhB;QAChB,MAAMI,OAAOL,kBAAkBC;QAE/B,IAAIiB;QACJ,IAAIL;QACJ,IAAI;YACAK,WAAW,MAAMC,MAAMF,KAAKZ;YAC5BQ,OAAO,MAAMK,SAAS,IAAI;QAC9B,EAAE,OAAOR,OAAO;YACZ,MAAMU,SAASX,UAAUC,SAAS,YAAY;YAC9C,OAAOI,OAAO,IAAI,CAAC,IAAIO,iBAAiB;gBAAEJ;gBAAKG;YAAO,GAAGV;QAC7D;QAEA,MAAMY,OAAOV,UAAUC;QAEvB,IAAI,CAACK,SAAS,EAAE,EAAE;YACd,MAAMK,cAAc,IAAIC,gBAAgB;gBACpCP;gBACA,QAAQC,SAAS,MAAM;gBACvB,YAAYA,SAAS,UAAU;gBAC/B,MAAMI,KAAK,IAAI,KAAKA,KAAK,KAAK,GAAGf;YACrC;YACA,OAAOO,OAAO,IAAI,CAACS;QACvB;QAEA,IAAID,KAAK,MAAM,IACX,OAAOR,OAAO,IAAI,CAAC,IAAIO,iBAAiB;YAAEJ;YAAK,QAAQ;QAAc,GAAGK,KAAK,KAAK;QAGtF,OAAOR,OAAO,EAAE,CAACQ,KAAK,KAAK;IAC/B;AACJ;AAEO,MAAMG,wBAAaC,qBAAqB;IAC3C,aAAaC;IACb,gBAAgBX;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseError } from "@webiny/feature/api";
|
|
2
|
+
export interface HttpRequestErrorData {
|
|
3
|
+
url: string;
|
|
4
|
+
reason: "network" | "timeout" | "invalidJson";
|
|
5
|
+
}
|
|
6
|
+
/** The request didn't produce a usable response: it failed, timed out, or the body wasn't JSON. */
|
|
7
|
+
export declare class HttpRequestError extends BaseError<HttpRequestErrorData> {
|
|
8
|
+
readonly code: "HttpClient/Request";
|
|
9
|
+
constructor(data: HttpRequestErrorData, cause: unknown);
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseError } from "@webiny/feature/api";
|
|
2
|
+
class HttpRequestError extends BaseError {
|
|
3
|
+
constructor(data, cause){
|
|
4
|
+
super({
|
|
5
|
+
message: `Request to "${data.url}" failed (${data.reason}).`,
|
|
6
|
+
data
|
|
7
|
+
}), this.code = "HttpClient/Request";
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export { HttpRequestError };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=HttpRequestError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/httpClient/HttpRequestError.js","sources":["../../../src/features/httpClient/HttpRequestError.ts"],"sourcesContent":["import { BaseError } from \"@webiny/feature/api\";\n\nexport interface HttpRequestErrorData {\n url: string;\n reason: \"network\" | \"timeout\" | \"invalidJson\";\n}\n\n/** The request didn't produce a usable response: it failed, timed out, or the body wasn't JSON. */\nexport class HttpRequestError extends BaseError<HttpRequestErrorData> {\n override readonly code = \"HttpClient/Request\" as const;\n\n constructor(data: HttpRequestErrorData, cause: unknown) {\n super({\n message: `Request to \"${data.url}\" failed (${data.reason}).`,\n data\n });\n this.cause = cause;\n }\n}\n"],"names":["HttpRequestError","BaseError","data","cause"],"mappings":";AAQO,MAAMA,yBAAyBC;IAGlC,YAAYC,IAA0B,EAAEC,KAAc,CAAE;QACpD,KAAK,CAAC;YACF,SAAS,CAAC,YAAY,EAAED,KAAK,GAAG,CAAC,UAAU,EAAEA,KAAK,MAAM,CAAC,EAAE,CAAC;YAC5DA;QACJ,SANc,IAAI,GAAG;QAOrB,IAAI,CAAC,KAAK,GAAGC;IACjB;AACJ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseError } from "@webiny/feature/api";
|
|
2
|
+
export interface HttpStatusErrorData {
|
|
3
|
+
url: string;
|
|
4
|
+
status: number;
|
|
5
|
+
statusText: string;
|
|
6
|
+
body: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** The server answered with a non-2xx status. */
|
|
9
|
+
export declare class HttpStatusError extends BaseError<HttpStatusErrorData> {
|
|
10
|
+
readonly code: "HttpClient/Status";
|
|
11
|
+
constructor(data: HttpStatusErrorData);
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseError } from "@webiny/feature/api";
|
|
2
|
+
class HttpStatusError extends BaseError {
|
|
3
|
+
constructor(data){
|
|
4
|
+
super({
|
|
5
|
+
message: `Request to "${data.url}" failed with status ${data.status}.`,
|
|
6
|
+
data
|
|
7
|
+
}), this.code = "HttpClient/Status";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export { HttpStatusError };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=HttpStatusError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/httpClient/HttpStatusError.js","sources":["../../../src/features/httpClient/HttpStatusError.ts"],"sourcesContent":["import { BaseError } from \"@webiny/feature/api\";\n\nexport interface HttpStatusErrorData {\n url: string;\n status: number;\n statusText: string;\n body: unknown; // the parsed JSON body, or `undefined` when there was none\n}\n\n/** The server answered with a non-2xx status. */\nexport class HttpStatusError extends BaseError<HttpStatusErrorData> {\n override readonly code = \"HttpClient/Status\" as const;\n\n constructor(data: HttpStatusErrorData) {\n super({\n message: `Request to \"${data.url}\" failed with status ${data.status}.`,\n data\n });\n }\n}\n"],"names":["HttpStatusError","BaseError","data"],"mappings":";AAUO,MAAMA,wBAAwBC;IAGjC,YAAYC,IAAyB,CAAE;QACnC,KAAK,CAAC;YACF,SAAS,CAAC,YAAY,EAAEA,KAAK,GAAG,CAAC,qBAAqB,EAAEA,KAAK,MAAM,CAAC,CAAC,CAAC;YACtEA;QACJ,SANc,IAAI,GAAG;IAOzB;AACJ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Result } from "@webiny/feature/api";
|
|
2
|
+
import type { HttpRequestError } from "./HttpRequestError.js";
|
|
3
|
+
import type { HttpStatusError } from "./HttpStatusError.js";
|
|
4
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
5
|
+
export interface IHttpClientRequest {
|
|
6
|
+
url: string;
|
|
7
|
+
method?: HttpMethod;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface IHttpClientErrors {
|
|
13
|
+
request: HttpRequestError;
|
|
14
|
+
status: HttpStatusError;
|
|
15
|
+
}
|
|
16
|
+
type HttpClientError = IHttpClientErrors[keyof IHttpClientErrors];
|
|
17
|
+
export interface IHttpClient {
|
|
18
|
+
/**
|
|
19
|
+
* Sends a request and parses the response body as JSON. The body is `unknown` on purpose:
|
|
20
|
+
* validate it before use.
|
|
21
|
+
*/
|
|
22
|
+
requestJson(request: IHttpClientRequest): Promise<Result<unknown, HttpClientError>>;
|
|
23
|
+
}
|
|
24
|
+
/** Outbound HTTP requests with a timeout, a status check and JSON parsing. */
|
|
25
|
+
export declare const HttpClient: import("@webiny/di").Abstraction<IHttpClient>;
|
|
26
|
+
export declare namespace HttpClient {
|
|
27
|
+
type Interface = IHttpClient;
|
|
28
|
+
type Request = IHttpClientRequest;
|
|
29
|
+
type Method = HttpMethod;
|
|
30
|
+
type Error = HttpClientError;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/httpClient/abstractions.js","sources":["../../../src/features/httpClient/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/api\";\nimport type { Result } from \"@webiny/feature/api\";\nimport type { HttpRequestError } from \"./HttpRequestError.js\";\nimport type { HttpStatusError } from \"./HttpStatusError.js\";\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\nexport interface IHttpClientRequest {\n url: string;\n method?: HttpMethod; // defaults to \"GET\"\n headers?: Record<string, string>;\n body?: unknown; // sent as JSON\n timeoutMs?: number; // defaults to 10 seconds\n}\n\nexport interface IHttpClientErrors {\n request: HttpRequestError;\n status: HttpStatusError;\n}\n\ntype HttpClientError = IHttpClientErrors[keyof IHttpClientErrors];\n\nexport interface IHttpClient {\n /**\n * Sends a request and parses the response body as JSON. The body is `unknown` on purpose:\n * validate it before use.\n */\n requestJson(request: IHttpClientRequest): Promise<Result<unknown, HttpClientError>>;\n}\n\n/** Outbound HTTP requests with a timeout, a status check and JSON parsing. */\nexport const HttpClient = createAbstraction<IHttpClient>(\"HttpClient\");\n\nexport namespace HttpClient {\n export type Interface = IHttpClient;\n export type Request = IHttpClientRequest;\n export type Method = HttpMethod;\n export type Error = HttpClientError;\n}\n"],"names":["HttpClient","createAbstraction"],"mappings":";AA+BO,MAAMA,aAAaC,kBAA+B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/api";
|
|
2
|
+
import { HttpClient } from "./HttpClient.js";
|
|
3
|
+
const HttpClientFeature = createFeature({
|
|
4
|
+
name: "HttpClientFeature",
|
|
5
|
+
register (container) {
|
|
6
|
+
container.register(HttpClient).inSingletonScope();
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
export { HttpClientFeature };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/httpClient/feature.js","sources":["../../../src/features/httpClient/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { HttpClient } from \"./HttpClient.js\";\n\nexport const HttpClientFeature = createFeature({\n name: \"HttpClientFeature\",\n register(container) {\n container.register(HttpClient).inSingletonScope();\n }\n});\n"],"names":["HttpClientFeature","createFeature","container","HttpClient"],"mappings":";;AAGO,MAAMA,oBAAoBC,cAAc;IAC3C,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC,YAAY,gBAAgB;IACnD;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/users/shared/loaders.js","sources":["../../../../src/features/users/shared/loaders.ts"],"sourcesContent":["import DataLoader from \"dataloader\";\nimport WebinyError from \"@webiny/error\";\nimport flatten from \"lodash/flatten.js\";\nimport type { AdminUsersStorageOperations } from \"~/types/users.js\";\nimport type { AdminUser } from \"./types.js\";\n\ninterface Config {\n getTenant(): string;\n storageOperations: AdminUsersStorageOperations;\n}\n\ninterface GetUserLoaderKey {\n id: string;\n tenant: string;\n}\n\nexport const createUserLoaders = ({ storageOperations }: Config) => {\n const loaders = new Map<string, DataLoader<any, any>>();\n\n async function getUserLoader(ids: readonly GetUserLoaderKey[]) {\n if (ids.length === 0) {\n return [];\n }\n\n // Group by tenant\n const byTenant = ids.reduce(\n (acc, item) => {\n acc[item.tenant] = [...(acc[item.tenant] || []), item.id];\n return acc;\n },\n {} as Record<string, string[]>\n );\n\n try {\n const results = await Promise.all(\n Object.keys(byTenant).map(tenant => {\n return storageOperations.listUsers({\n where: {\n tenant,\n id_in: byTenant[tenant]\n }\n });\n })\n ).then(res => flatten(res));\n\n return ids.map(({ tenant, id }) => {\n const user = results.find(item => item.id === id && item.tenant === tenant);\n /**\n * Casting because DataLoader expect error. But we do not.\n */\n return user || (null as unknown as Error);\n });\n } catch (err) {\n throw WebinyError.from(err, {\n message: \"Could not execute batch read in getUser.\",\n code: \"BATCH_READ_ERROR\",\n data: { ids }\n });\n }\n }\n\n return {\n get getUser() {\n let userLoader = loaders.get(\"getUser\");\n if (userLoader) {\n return userLoader;\n }\n userLoader = new DataLoader<GetUserLoaderKey, AdminUser, string>(getUserLoader, {\n cacheKeyFn(key) {\n return `${key.tenant}:${key.id}`;\n }\n });\n loaders.set(\"getUser\", userLoader);\n return userLoader;\n },\n\n clearLoadersCache(ids: GetUserLoaderKey[]) {\n for (const id of ids) {\n this.getUser.clear(id);\n }\n },\n\n async updateDataLoaderUserCache(id: GetUserLoaderKey, data: Partial<AdminUser>) {\n const user = await this.getUser.load(id);\n this.getUser.clear(id).prime(id, Object.assign({}, user, data));\n }\n };\n};\n"],"names":["createUserLoaders","storageOperations","loaders","Map","getUserLoader","ids","byTenant","acc","item","results","Promise","Object","tenant","res","flatten","id","user","err","WebinyError","userLoader","DataLoader","key","data"],"mappings":";;;AAgBO,MAAMA,oBAAoB,CAAC,EAAEC,iBAAiB,EAAU;IAC3D,MAAMC,UAAU,IAAIC;IAEpB,eAAeC,cAAcC,GAAgC;QACzD,IAAIA,AAAe,MAAfA,IAAI,MAAM,EACV,OAAO,EAAE;QAIb,MAAMC,WAAWD,IAAI,MAAM,CACvB,CAACE,KAAKC;YACFD,GAAG,CAACC,KAAK,MAAM,CAAC,GAAG;mBAAKD,GAAG,CAACC,KAAK,MAAM,CAAC,IAAI,EAAE;gBAAGA,KAAK,EAAE;aAAC;YACzD,OAAOD;QACX,GACA,CAAC;QAGL,IAAI;YACA,MAAME,UAAU,MAAMC,QAAQ,GAAG,CAC7BC,OAAO,IAAI,CAACL,UAAU,GAAG,CAACM,CAAAA,SACfX,kBAAkB,SAAS,CAAC;oBAC/B,OAAO;wBACHW;wBACA,OAAON,QAAQ,CAACM,OAAO;oBAC3B;gBACJ,KAEN,IAAI,CAACC,CAAAA,MAAOC,QAAQD;YAEtB,OAAOR,IAAI,GAAG,CAAC,CAAC,EAAEO,MAAM,EAAEG,EAAE,EAAE;gBAC1B,MAAMC,OAAOP,QAAQ,IAAI,CAACD,CAAAA,OAAQA,KAAK,EAAE,KAAKO,MAAMP,KAAK,MAAM,KAAKI;gBAIpE,OAAOI,QAAS;YACpB;QACJ,EAAE,OAAOC,KAAK;YACV,MAAMC,MAAAA,IAAgB,CAACD,KAAK;gBACxB,SAAS;gBACT,MAAM;gBACN,MAAM;oBAAEZ;gBAAI;YAChB;QACJ;IACJ;IAEA,OAAO;QACH,IAAI
|
|
1
|
+
{"version":3,"file":"features/users/shared/loaders.js","sources":["../../../../src/features/users/shared/loaders.ts"],"sourcesContent":["import DataLoader from \"dataloader\";\nimport WebinyError from \"@webiny/error\";\nimport flatten from \"lodash/flatten.js\";\nimport type { AdminUsersStorageOperations } from \"~/types/users.js\";\nimport type { AdminUser } from \"./types.js\";\n\ninterface Config {\n getTenant(): string;\n storageOperations: AdminUsersStorageOperations;\n}\n\ninterface GetUserLoaderKey {\n id: string;\n tenant: string;\n}\n\nexport const createUserLoaders = ({ storageOperations }: Config) => {\n const loaders = new Map<string, DataLoader<any, any>>();\n\n async function getUserLoader(ids: readonly GetUserLoaderKey[]) {\n if (ids.length === 0) {\n return [];\n }\n\n // Group by tenant\n const byTenant = ids.reduce(\n (acc, item) => {\n acc[item.tenant] = [...(acc[item.tenant] || []), item.id];\n return acc;\n },\n {} as Record<string, string[]>\n );\n\n try {\n const results = await Promise.all(\n Object.keys(byTenant).map(tenant => {\n return storageOperations.listUsers({\n where: {\n tenant,\n id_in: byTenant[tenant]\n }\n });\n })\n ).then(res => flatten(res));\n\n return ids.map(({ tenant, id }) => {\n const user = results.find(item => item.id === id && item.tenant === tenant);\n /**\n * Casting because DataLoader expect error. But we do not.\n */\n return user || (null as unknown as Error);\n });\n } catch (err) {\n throw WebinyError.from(err, {\n message: \"Could not execute batch read in getUser.\",\n code: \"BATCH_READ_ERROR\",\n data: { ids }\n });\n }\n }\n\n return {\n get getUser() {\n let userLoader = loaders.get(\"getUser\");\n if (userLoader) {\n return userLoader;\n }\n userLoader = new DataLoader<GetUserLoaderKey, AdminUser, string>(getUserLoader, {\n cacheKeyFn(key) {\n return `${key.tenant}:${key.id}`;\n }\n });\n loaders.set(\"getUser\", userLoader);\n return userLoader;\n },\n\n clearLoadersCache(ids: GetUserLoaderKey[]) {\n for (const id of ids) {\n this.getUser.clear(id);\n }\n },\n\n async updateDataLoaderUserCache(id: GetUserLoaderKey, data: Partial<AdminUser>) {\n const user = await this.getUser.load(id);\n this.getUser.clear(id).prime(id, Object.assign({}, user, data));\n }\n };\n};\n"],"names":["createUserLoaders","storageOperations","loaders","Map","getUserLoader","ids","byTenant","acc","item","results","Promise","Object","tenant","res","flatten","id","user","err","WebinyError","userLoader","DataLoader","key","data"],"mappings":";;;AAgBO,MAAMA,oBAAoB,CAAC,EAAEC,iBAAiB,EAAU;IAC3D,MAAMC,UAAU,IAAIC;IAEpB,eAAeC,cAAcC,GAAgC;QACzD,IAAIA,AAAe,MAAfA,IAAI,MAAM,EACV,OAAO,EAAE;QAIb,MAAMC,WAAWD,IAAI,MAAM,CACvB,CAACE,KAAKC;YACFD,GAAG,CAACC,KAAK,MAAM,CAAC,GAAG;mBAAKD,GAAG,CAACC,KAAK,MAAM,CAAC,IAAI,EAAE;gBAAGA,KAAK,EAAE;aAAC;YACzD,OAAOD;QACX,GACA,CAAC;QAGL,IAAI;YACA,MAAME,UAAU,MAAMC,QAAQ,GAAG,CAC7BC,OAAO,IAAI,CAACL,UAAU,GAAG,CAACM,CAAAA,SACfX,kBAAkB,SAAS,CAAC;oBAC/B,OAAO;wBACHW;wBACA,OAAON,QAAQ,CAACM,OAAO;oBAC3B;gBACJ,KAEN,IAAI,CAACC,CAAAA,MAAOC,QAAQD;YAEtB,OAAOR,IAAI,GAAG,CAAC,CAAC,EAAEO,MAAM,EAAEG,EAAE,EAAE;gBAC1B,MAAMC,OAAOP,QAAQ,IAAI,CAACD,CAAAA,OAAQA,KAAK,EAAE,KAAKO,MAAMP,KAAK,MAAM,KAAKI;gBAIpE,OAAOI,QAAS;YACpB;QACJ,EAAE,OAAOC,KAAK;YACV,MAAMC,MAAAA,IAAgB,CAACD,KAAK;gBACxB,SAAS;gBACT,MAAM;gBACN,MAAM;oBAAEZ;gBAAI;YAChB;QACJ;IACJ;IAEA,OAAO;QACH,IAAI;YACA,IAAIc,aAAajB,QAAQ,GAAG,CAAC;YAC7B,IAAIiB,YACA,OAAOA;YAEXA,aAAa,IAAIC,WAAgDhB,eAAe;gBAC5E,YAAWiB,GAAG;oBACV,OAAO,GAAGA,IAAI,MAAM,CAAC,CAAC,EAAEA,IAAI,EAAE,EAAE;gBACpC;YACJ;YACAnB,QAAQ,GAAG,CAAC,WAAWiB;YACvB,OAAOA;QACX;QAEA,mBAAkBd,GAAuB;YACrC,KAAK,MAAMU,MAAMV,IACb,IAAI,CAAC,OAAO,CAAC,KAAK,CAACU;QAE3B;QAEA,MAAM,2BAA0BA,EAAoB,EAAEO,IAAwB;YAC1E,MAAMN,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAACD;YACrC,IAAI,CAAC,OAAO,CAAC,KAAK,CAACA,IAAI,KAAK,CAACA,IAAIJ,OAAO,MAAM,CAAC,CAAC,GAAGK,MAAMM;QAC7D;IACJ;AACJ"}
|
package/idp/JwksCache.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Jwk } from "../features/security/utils/verifyJwtUsingJwk.js";
|
|
2
|
+
import { HttpClient } from "../features/httpClient/index.js";
|
|
2
3
|
import { JwkCache } from "./abstractions.js";
|
|
3
|
-
|
|
4
|
+
declare class JwksCacheImpl implements JwkCache.Interface {
|
|
5
|
+
private readonly httpClient;
|
|
4
6
|
private cache;
|
|
7
|
+
constructor(httpClient: HttpClient.Interface);
|
|
5
8
|
getKeys(issuer: string): Promise<Jwk[]>;
|
|
6
|
-
|
|
9
|
+
private fetchJson;
|
|
7
10
|
}
|
|
11
|
+
export declare const JwksCache: import("@webiny/di").Implementation<typeof JwksCacheImpl>;
|
|
12
|
+
export {};
|
package/idp/JwksCache.js
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
|
-
|
|
1
|
+
import { createImplementation } from "@webiny/feature/api";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { HttpClient } from "../features/httpClient/index.js";
|
|
4
|
+
import { JwkCache } from "./abstractions.js";
|
|
5
|
+
const oidcConfigurationSchema = z.object({
|
|
6
|
+
jwks_uri: z.string()
|
|
7
|
+
});
|
|
8
|
+
const jwksSchema = z.object({
|
|
9
|
+
keys: z.array(z.looseObject({
|
|
10
|
+
kid: z.string().optional()
|
|
11
|
+
}))
|
|
12
|
+
});
|
|
13
|
+
function getOpenidConfigurationUrl(issuer) {
|
|
14
|
+
const openidUrl = new URL(issuer);
|
|
15
|
+
const pathname = openidUrl.pathname + "/.well-known/openid-configuration";
|
|
16
|
+
openidUrl.pathname = pathname.replace(/\/+/g, "/");
|
|
17
|
+
return openidUrl.toString();
|
|
18
|
+
}
|
|
19
|
+
class JwksCacheImpl {
|
|
20
|
+
constructor(httpClient){
|
|
21
|
+
this.httpClient = httpClient;
|
|
22
|
+
this.cache = new Map();
|
|
23
|
+
}
|
|
2
24
|
async getKeys(issuer) {
|
|
3
25
|
const cached = this.cache.get(issuer);
|
|
4
|
-
if (cached) return cached
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const oidcConfig = await fetch(openidUrl.toString()).then((res)=>res.json());
|
|
9
|
-
const jwksResponse = await fetch(oidcConfig.jwks_uri).then((res)=>res.json());
|
|
26
|
+
if (cached) return cached;
|
|
27
|
+
const openidConfigurationUrl = getOpenidConfigurationUrl(issuer);
|
|
28
|
+
const oidcConfig = await this.fetchJson(openidConfigurationUrl, oidcConfigurationSchema);
|
|
29
|
+
const jwksResponse = await this.fetchJson(oidcConfig.jwks_uri, jwksSchema);
|
|
10
30
|
const jwks = jwksResponse.keys;
|
|
11
|
-
this.cache.set(issuer,
|
|
12
|
-
oidcConfig,
|
|
13
|
-
jwks
|
|
14
|
-
});
|
|
31
|
+
this.cache.set(issuer, jwks);
|
|
15
32
|
return jwks;
|
|
16
33
|
}
|
|
17
|
-
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
async fetchJson(url, schema) {
|
|
35
|
+
const response = await this.httpClient.requestJson({
|
|
36
|
+
url
|
|
37
|
+
});
|
|
38
|
+
if (response.isFail()) throw response.error;
|
|
39
|
+
const result = schema.safeParse(response.value);
|
|
40
|
+
if (!result.success) throw new Error(`Invalid response from "${url}": ${result.error.message}`);
|
|
41
|
+
return result.data;
|
|
22
42
|
}
|
|
23
43
|
}
|
|
44
|
+
const JwksCache = createImplementation({
|
|
45
|
+
abstraction: JwkCache,
|
|
46
|
+
implementation: JwksCacheImpl,
|
|
47
|
+
dependencies: [
|
|
48
|
+
HttpClient
|
|
49
|
+
]
|
|
50
|
+
});
|
|
24
51
|
export { JwksCache };
|
|
25
52
|
|
|
26
53
|
//# sourceMappingURL=JwksCache.js.map
|
package/idp/JwksCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idp/JwksCache.js","sources":["../../src/idp/JwksCache.ts"],"sourcesContent":["import type { Jwk } from \"~/features/security/utils/verifyJwtUsingJwk.js\";\nimport { JwkCache } from \"./abstractions.js\";\n\
|
|
1
|
+
{"version":3,"file":"idp/JwksCache.js","sources":["../../src/idp/JwksCache.ts"],"sourcesContent":["import { createImplementation } from \"@webiny/feature/api\";\nimport { z } from \"zod\";\nimport type { Jwk } from \"~/features/security/utils/verifyJwtUsingJwk.js\";\nimport { HttpClient } from \"~/features/httpClient/index.js\";\nimport { JwkCache } from \"./abstractions.js\";\n\nconst oidcConfigurationSchema = z.object({\n jwks_uri: z.string()\n});\n\nconst jwksSchema = z.object({\n keys: z.array(z.looseObject({ kid: z.string().optional() }))\n});\n\nfunction getOpenidConfigurationUrl(issuer: string): string {\n const openidUrl = new URL(issuer);\n const pathname = openidUrl.pathname + \"/.well-known/openid-configuration\";\n openidUrl.pathname = pathname.replace(/\\/+/g, \"/\");\n return openidUrl.toString();\n}\n\nclass JwksCacheImpl implements JwkCache.Interface {\n private cache = new Map<string, Jwk[]>();\n\n constructor(private readonly httpClient: HttpClient.Interface) {}\n\n async getKeys(issuer: string): Promise<Jwk[]> {\n const cached = this.cache.get(issuer);\n if (cached) {\n return cached;\n }\n\n const openidConfigurationUrl = getOpenidConfigurationUrl(issuer);\n const oidcConfig = await this.fetchJson(openidConfigurationUrl, oidcConfigurationSchema);\n const jwksResponse = await this.fetchJson(oidcConfig.jwks_uri, jwksSchema);\n const jwks: Jwk[] = jwksResponse.keys;\n\n this.cache.set(issuer, jwks);\n\n return jwks;\n }\n\n // Throws, so a failed fetch fails the token verification instead of caching a bad answer.\n private async fetchJson<T>(url: string, schema: z.ZodType<T>): Promise<T> {\n const response = await this.httpClient.requestJson({ url });\n if (response.isFail()) {\n throw response.error;\n }\n\n const result = schema.safeParse(response.value);\n if (!result.success) {\n throw new Error(`Invalid response from \"${url}\": ${result.error.message}`);\n }\n\n return result.data;\n }\n}\n\nexport const JwksCache = createImplementation({\n abstraction: JwkCache,\n implementation: JwksCacheImpl,\n dependencies: [HttpClient]\n});\n"],"names":["oidcConfigurationSchema","z","jwksSchema","getOpenidConfigurationUrl","issuer","openidUrl","URL","pathname","JwksCacheImpl","httpClient","Map","cached","openidConfigurationUrl","oidcConfig","jwksResponse","jwks","url","schema","response","result","Error","JwksCache","createImplementation","JwkCache","HttpClient"],"mappings":";;;;AAMA,MAAMA,0BAA0BC,EAAE,MAAM,CAAC;IACrC,UAAUA,EAAE,MAAM;AACtB;AAEA,MAAMC,aAAaD,EAAE,MAAM,CAAC;IACxB,MAAMA,EAAE,KAAK,CAACA,EAAE,WAAW,CAAC;QAAE,KAAKA,EAAE,MAAM,GAAG,QAAQ;IAAG;AAC7D;AAEA,SAASE,0BAA0BC,MAAc;IAC7C,MAAMC,YAAY,IAAIC,IAAIF;IAC1B,MAAMG,WAAWF,UAAU,QAAQ,GAAG;IACtCA,UAAU,QAAQ,GAAGE,SAAS,OAAO,CAAC,QAAQ;IAC9C,OAAOF,UAAU,QAAQ;AAC7B;AAEA,MAAMG;IAGF,YAA6BC,UAAgC,CAAE;aAAlCA,UAAU,GAAVA;aAFrB,KAAK,GAAG,IAAIC;IAE4C;IAEhE,MAAM,QAAQN,MAAc,EAAkB;QAC1C,MAAMO,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,CAACP;QAC9B,IAAIO,QACA,OAAOA;QAGX,MAAMC,yBAAyBT,0BAA0BC;QACzD,MAAMS,aAAa,MAAM,IAAI,CAAC,SAAS,CAACD,wBAAwBZ;QAChE,MAAMc,eAAe,MAAM,IAAI,CAAC,SAAS,CAACD,WAAW,QAAQ,EAAEX;QAC/D,MAAMa,OAAcD,aAAa,IAAI;QAErC,IAAI,CAAC,KAAK,CAAC,GAAG,CAACV,QAAQW;QAEvB,OAAOA;IACX;IAGA,MAAc,UAAaC,GAAW,EAAEC,MAAoB,EAAc;QACtE,MAAMC,WAAW,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAEF;QAAI;QACzD,IAAIE,SAAS,MAAM,IACf,MAAMA,SAAS,KAAK;QAGxB,MAAMC,SAASF,OAAO,SAAS,CAACC,SAAS,KAAK;QAC9C,IAAI,CAACC,OAAO,OAAO,EACf,MAAM,IAAIC,MAAM,CAAC,uBAAuB,EAAEJ,IAAI,GAAG,EAAEG,OAAO,KAAK,CAAC,OAAO,EAAE;QAG7E,OAAOA,OAAO,IAAI;IACtB;AACJ;AAEO,MAAME,YAAYC,qBAAqB;IAC1C,aAAaC;IACb,gBAAgBf;IAChB,cAAc;QAACgB;KAAW;AAC9B"}
|
package/idp/feature.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createFeature } from "@webiny/feature/api";
|
|
2
2
|
import { JwtAuthenticator } from "./JwtAuthenticator.js";
|
|
3
|
-
import { JwkCache } from "./abstractions.js";
|
|
4
3
|
import { JwksCache } from "./JwksCache.js";
|
|
5
4
|
import { OidcJwtIdentityProvider } from "./OidcJwtIdentityProvider.js";
|
|
6
5
|
const IdpAuthenticatorFeature = createFeature({
|
|
@@ -8,7 +7,7 @@ const IdpAuthenticatorFeature = createFeature({
|
|
|
8
7
|
register (container) {
|
|
9
8
|
container.register(JwtAuthenticator);
|
|
10
9
|
container.register(OidcJwtIdentityProvider);
|
|
11
|
-
container.
|
|
10
|
+
container.register(JwksCache).inSingletonScope();
|
|
12
11
|
}
|
|
13
12
|
});
|
|
14
13
|
export { IdpAuthenticatorFeature };
|
package/idp/feature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idp/feature.js","sources":["../../src/idp/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { JwtAuthenticator } from \"./JwtAuthenticator.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"idp/feature.js","sources":["../../src/idp/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { JwtAuthenticator } from \"./JwtAuthenticator.js\";\nimport { JwksCache } from \"./JwksCache.js\";\nimport { OidcJwtIdentityProvider } from \"~/idp/OidcJwtIdentityProvider.js\";\n\nexport const IdpAuthenticatorFeature = createFeature({\n name: \"IdpAuthenticator\",\n register(container) {\n container.register(JwtAuthenticator);\n container.register(OidcJwtIdentityProvider);\n // Singleton, so the keys stay cached for the life of the process.\n container.register(JwksCache).inSingletonScope();\n }\n});\n"],"names":["IdpAuthenticatorFeature","createFeature","container","JwtAuthenticator","OidcJwtIdentityProvider","JwksCache"],"mappings":";;;;AAKO,MAAMA,0BAA0BC,cAAc;IACjD,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;QACnBD,UAAU,QAAQ,CAACE;QAEnBF,UAAU,QAAQ,CAACG,WAAW,gBAAgB;IAClD;AACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/api-core",
|
|
3
|
-
"version": "6.5.0-beta.
|
|
3
|
+
"version": "6.5.0-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,38 +14,38 @@
|
|
|
14
14
|
"./*": "./*"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@ai-sdk/anthropic": "4.0.
|
|
18
|
-
"@ai-sdk/google": "4.0.
|
|
19
|
-
"@ai-sdk/openai": "4.0.
|
|
20
|
-
"@webiny/api": "6.5.0-beta.
|
|
21
|
-
"@webiny/di": "1.0
|
|
22
|
-
"@webiny/error": "6.5.0-beta.
|
|
23
|
-
"@webiny/feature": "6.5.0-beta.
|
|
24
|
-
"@webiny/feature-flags": "6.5.0-beta.
|
|
25
|
-
"@webiny/handler": "6.5.0-beta.
|
|
26
|
-
"@webiny/handler-db": "6.5.0-beta.
|
|
27
|
-
"@webiny/handler-graphql": "6.5.0-beta.
|
|
28
|
-
"@webiny/plugins": "6.5.0-beta.
|
|
29
|
-
"@webiny/utils": "6.5.0-beta.
|
|
30
|
-
"@webiny/wcp": "6.5.0-beta.
|
|
31
|
-
"ai": "7.0.
|
|
17
|
+
"@ai-sdk/anthropic": "4.0.65",
|
|
18
|
+
"@ai-sdk/google": "4.0.82",
|
|
19
|
+
"@ai-sdk/openai": "4.0.78",
|
|
20
|
+
"@webiny/api": "6.5.0-beta.6",
|
|
21
|
+
"@webiny/di": "1.1.0",
|
|
22
|
+
"@webiny/error": "6.5.0-beta.6",
|
|
23
|
+
"@webiny/feature": "6.5.0-beta.6",
|
|
24
|
+
"@webiny/feature-flags": "6.5.0-beta.6",
|
|
25
|
+
"@webiny/handler": "6.5.0-beta.6",
|
|
26
|
+
"@webiny/handler-db": "6.5.0-beta.6",
|
|
27
|
+
"@webiny/handler-graphql": "6.5.0-beta.6",
|
|
28
|
+
"@webiny/plugins": "6.5.0-beta.6",
|
|
29
|
+
"@webiny/utils": "6.5.0-beta.6",
|
|
30
|
+
"@webiny/wcp": "6.5.0-beta.6",
|
|
31
|
+
"ai": "7.0.118",
|
|
32
32
|
"dataloader": "2.2.3",
|
|
33
|
-
"jose": "6.2.
|
|
33
|
+
"jose": "6.2.12",
|
|
34
34
|
"jsonwebtoken": "9.0.3",
|
|
35
35
|
"lodash": "4.18.1",
|
|
36
|
-
"minimatch": "10.2.
|
|
36
|
+
"minimatch": "10.2.6",
|
|
37
37
|
"pino": "10.3.1",
|
|
38
38
|
"pino-lambda": "4.4.1",
|
|
39
|
-
"zod": "4.
|
|
39
|
+
"zod": "4.6.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/jsonwebtoken": "9.0.10",
|
|
43
|
-
"@webiny/build-tools": "6.5.0-beta.
|
|
44
|
-
"@webiny/handler-aws": "6.5.0-beta.
|
|
45
|
-
"@webiny/project-utils": "6.5.0-beta.
|
|
43
|
+
"@webiny/build-tools": "6.5.0-beta.6",
|
|
44
|
+
"@webiny/handler-aws": "6.5.0-beta.6",
|
|
45
|
+
"@webiny/project-utils": "6.5.0-beta.6",
|
|
46
46
|
"rimraf": "6.1.3",
|
|
47
47
|
"typescript": "7.0.2",
|
|
48
|
-
"vitest": "
|
|
48
|
+
"vitest": "5.0.2"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|