Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ on:

env:
WEAVIATE_124: 1.24.26
WEAVIATE_125: 1.25.25
WEAVIATE_126: 1.26.10
WEAVIATE_127: 1.27.3
WEAVIATE_125: 1.25.28
WEAVIATE_126: 1.26.13
WEAVIATE_127: 1.27.8
WEAVIATE_128: 1.28.0

jobs:
checks:
Expand Down
5 changes: 4 additions & 1 deletion src/collections/config/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ export type MultiTenancyConfig = {
enabled: boolean;
};

export type ReplicationDeletionStrategy = 'DeleteOnConflict' | 'NoAutomatedResolution';
export type ReplicationDeletionStrategy =
| 'DeleteOnConflict'
| 'NoAutomatedResolution'
| 'TimeBasedResolution';

export type ReplicationConfig = {
asyncEnabled: boolean;
Expand Down
4 changes: 1 addition & 3 deletions src/collections/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,7 @@ describe('Testing of the collections.create method', () => {

expect(response.replication.asyncEnabled).toEqual(false);
expect(response.replication.deletionStrategy).toEqual<ReplicationDeletionStrategy>(
(await cluster.getWeaviateVersion().then((ver) => ver.isLowerThan(1, 25, 0)))
? 'NoAutomatedResolution'
: 'DeleteOnConflict'
'NoAutomatedResolution'
);
expect(response.replication.factor).toEqual(2);

Expand Down
8 changes: 5 additions & 3 deletions src/connection/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,15 @@ export function connectToCustom(
}

function addWeaviateEmbeddingServiceHeaders(clusterURL: string, options?: ConnectToWeaviateCloudOptions) {
if (!isApiKey(options?.authCredentials)) {
const creds = options?.authCredentials;

if (!isApiKey(creds)) {
return options?.headers;
}

return {
...options.headers,
'X-Weaviate-Api-Key': mapApiKey(options.authCredentials).apiKey,
...options?.headers,
'X-Weaviate-Api-Key': mapApiKey(creds).apiKey,
'X-Weaviate-Cluster-Url': clusterURL,
};
}
22 changes: 15 additions & 7 deletions src/connection/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { Agent } from 'http';
import OpenidConfigurationGetter from '../misc/openidConfigurationGetter.js';

import {
WeaviateInsufficientPermissionsError,
WeaviateInvalidInputError,
WeaviateRequestTimeoutError,
WeaviateUnauthenticatedError,
WeaviateUnexpectedStatusCodeError,
} from '../errors.js';
import {
Expand Down Expand Up @@ -155,11 +157,11 @@ export default class ConnectionREST {
return this.http.head(path, payload);
};

get = (path: string, expectReturnContent = true) => {
get = <T>(path: string, expectReturnContent = true) => {
if (this.authEnabled) {
return this.login().then((token) => this.http.get(path, expectReturnContent, token));
return this.login().then((token) => this.http.get<T>(path, expectReturnContent, token));
}
return this.http.get(path, expectReturnContent);
return this.http.get<T>(path, expectReturnContent);
};

login = async () => {
Expand Down Expand Up @@ -197,7 +199,7 @@ export interface HttpClient {
expectReturnContent: boolean,
bearerToken: string
) => Promise<T | undefined>;
get: (path: string, expectReturnContent?: boolean, bearerToken?: string) => any;
get: <T>(path: string, expectReturnContent?: boolean, bearerToken?: string) => Promise<T>;
externalPost: (externalUrl: string, body: any, contentType: any) => any;
getRaw: (path: string, bearerToken?: string) => any;
delete: (path: string, payload: any, expectReturnContent?: boolean, bearerToken?: string) => any;
Expand Down Expand Up @@ -313,7 +315,7 @@ export const httpClient = (config: InternalConnectionParams): HttpClient => {
handleHeadResponse<undefined>(false)
);
},
get: <T>(path: string, expectReturnContent = true, bearerToken = ''): Promise<T | undefined> => {
get: <T>(path: string, expectReturnContent = true, bearerToken = ''): Promise<T> => {
const request = {
method: 'GET',
headers: {
Expand All @@ -323,7 +325,7 @@ export const httpClient = (config: InternalConnectionParams): HttpClient => {
};
addAuthHeaderIfNeeded(request, bearerToken);
return fetchWithTimeout(url(path), config.timeout?.query || 30, request).then(
checkStatus<T>(expectReturnContent)
checkStatus<any>(expectReturnContent)
);
},
getRaw: (path: string, bearerToken = '') => {
Expand Down Expand Up @@ -380,7 +382,13 @@ const checkStatus =
} catch (e) {
err = errText;
}
return Promise.reject(new WeaviateUnexpectedStatusCodeError(res.status, err));
if (res.status === 401) {
return Promise.reject(new WeaviateUnauthenticatedError(err));
} else if (res.status === 403) {
return Promise.reject(new WeaviateInsufficientPermissionsError(403, err));
} else {
return Promise.reject(new WeaviateUnexpectedStatusCodeError(res.status, err));
}
});
}
if (expectResponseBody) {
Expand Down
2 changes: 1 addition & 1 deletion src/data/journey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('data', () => {
.do()
.catch((e: Error) => {
expect(e.message).toEqual(
`The request to Weaviate failed with status code: 422 and message: {"error":[{"message":"invalid object: invalid text property 'stringProp' on class 'DataJourneyTestThing': not a string, but json.Number"}]}`
`The request to Weaviate failed with status code: 500 and message: {"error":[{"message":"invalid text property 'stringProp' on class 'DataJourneyTestThing': not a string, but json.Number"}]}`
);
});
});
Expand Down
17 changes: 17 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,20 @@ export class WeaviateRequestTimeoutError extends WeaviateError {
super(`Weaviate request timed out with message: ${message}`);
}
}

/**
* Is thrown if a request to Weaviate fails with a forbidden status code due to insufficient permissions.
*/
export class WeaviateInsufficientPermissionsError extends WeaviateError {
public code: number;
constructor(code: number, message: string) {
super(`Forbidden: ${message}`);
this.code = code;
}
}

export class WeaviateUnauthenticatedError extends WeaviateError {
constructor(message: string) {
super(`Unauthenticated: ${message}`);
}
}
14 changes: 12 additions & 2 deletions src/grpc/batcher.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Metadata } from 'nice-grpc';
import { Metadata, ServerError, Status } from 'nice-grpc';

import { ConsistencyLevel } from '../data/index.js';

import { BatchObject, BatchObjectsReply, BatchObjectsRequest } from '../proto/v1/batch.js';
import { WeaviateClient } from '../proto/v1/weaviate.js';

import { RetryOptions } from 'nice-grpc-client-middleware-retry';
import { WeaviateBatchError, WeaviateDeleteManyError } from '../errors.js';
import {
WeaviateBatchError,
WeaviateDeleteManyError,
WeaviateInsufficientPermissionsError,
} from '../errors.js';
import { Filters } from '../proto/v1/base.js';
import { BatchDeleteReply, BatchDeleteRequest } from '../proto/v1/batch_delete.js';
import Base from './base.js';
Expand Down Expand Up @@ -58,6 +62,9 @@ export default class Batcher extends Base implements Batch {
}
)
).catch((err) => {
if (err instanceof ServerError && err.code === Status.PERMISSION_DENIED) {
throw new WeaviateInsufficientPermissionsError(7, err.message);
}
throw new WeaviateDeleteManyError(err.message);
});
}
Expand All @@ -77,6 +84,9 @@ export default class Batcher extends Base implements Batch {
}
)
.catch((err) => {
if (err instanceof ServerError && err.code === Status.PERMISSION_DENIED) {
throw new WeaviateInsufficientPermissionsError(7, err.message);
}
throw new WeaviateBatchError(err.message);
})
);
Expand Down
9 changes: 6 additions & 3 deletions src/grpc/searcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConsistencyLevel } from '../data/index.js';

import { Metadata } from 'nice-grpc';
import { Metadata, ServerError, Status } from 'nice-grpc';
import { Filters } from '../proto/v1/base.js';
import {
BM25,
Expand All @@ -9,8 +9,8 @@ import {
MetadataRequest,
NearAudioSearch,
NearDepthSearch,
NearImageSearch,
NearIMUSearch,
NearImageSearch,
NearObject,
NearTextSearch,
NearThermalSearch,
Expand All @@ -25,7 +25,7 @@ import {
import { WeaviateClient } from '../proto/v1/weaviate.js';

import { RetryOptions } from 'nice-grpc-client-middleware-retry';
import { WeaviateQueryError } from '../errors.js';
import { WeaviateInsufficientPermissionsError, WeaviateQueryError } from '../errors.js';
import { GenerativeSearch } from '../proto/v1/generative.js';
import Base from './base.js';
import { retryOptions } from './retry.js';
Expand Down Expand Up @@ -157,6 +157,9 @@ export default class Searcher extends Base implements Search {
}
)
.catch((err) => {
if (err instanceof ServerError && err.code === Status.PERMISSION_DENIED) {
throw new WeaviateInsufficientPermissionsError(7, err.message);
}
throw new WeaviateQueryError(err.message, 'gRPC');
})
);
Expand Down
32 changes: 20 additions & 12 deletions src/grpc/tenantsManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Metadata } from 'nice-grpc';
import { Metadata, ServerError, Status } from 'nice-grpc';
import { RetryOptions } from 'nice-grpc-client-middleware-retry';
import { WeaviateDeleteManyError, WeaviateInsufficientPermissionsError } from '../errors.js';
import { TenantsGetReply, TenantsGetRequest } from '../proto/v1/tenants.js';
import { WeaviateClient } from '../proto/v1/weaviate.js';
import Base from './base.js';
Expand Down Expand Up @@ -28,17 +29,24 @@ export default class TenantsManager extends Base implements Tenants {

private call(message: TenantsGetRequest) {
return this.sendWithTimeout((signal: AbortSignal) =>
this.connection.tenantsGet(
{
...message,
collection: this.collection,
},
{
metadata: this.metadata,
signal,
...retryOptions,
}
)
this.connection
.tenantsGet(
{
...message,
collection: this.collection,
},
{
metadata: this.metadata,
signal,
...retryOptions,
}
)
.catch((err) => {
if (err instanceof ServerError && err.code === Status.PERMISSION_DENIED) {
throw new WeaviateInsufficientPermissionsError(7, err.message);
}
throw new WeaviateDeleteManyError(err.message);
})
);
}
}
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ProxiesParams, TimeoutParams } from './connection/http.js';
import { ConnectionGRPC } from './connection/index.js';
import MetaGetter from './misc/metaGetter.js';
import { Meta } from './openapi/types.js';
import roles, { Roles, permissions } from './roles/index.js';
import { DbVersion } from './utils/dbVersion.js';

import { Agent as HttpAgent } from 'http';
Expand Down Expand Up @@ -103,6 +104,7 @@ export interface WeaviateClient {
cluster: Cluster;
collections: Collections;
oidcAuth?: OidcAuthenticator;
roles: Roles;

close: () => Promise<void>;
getMeta: () => Promise<Meta>;
Expand Down Expand Up @@ -221,6 +223,7 @@ async function client(params: ClientParams): Promise<WeaviateClient> {
backup: backup(connection),
cluster: cluster(connection),
collections: collections(connection, dbVersionSupport),
roles: roles(connection),
close: () => Promise.resolve(connection.close()), // hedge against future changes to add I/O to .close()
getMeta: () => new MetaGetter(connection).do(),
getOpenIDConfig: () => new OpenidConfigurationGetter(connection.http).do(),
Expand All @@ -247,11 +250,13 @@ const app = {
configure,
configGuards,
reconfigure,
permissions,
};

export default app;
export * from './collections/index.js';
export * from './connection/index.js';
export * from './roles/types.js';
export * from './utils/base64.js';
export * from './utils/uuid.js';
export {
Expand Down
Loading
Loading