Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add 'Viewer' class disentangled from 'User' #48

Merged
merged 14 commits into from
Jul 31, 2024
13 changes: 13 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 0.11.0

- This PR disentangles two concepts in the module:

1. The AtlasUser in the system, which is a set of information like an e-mail, a name, a profile picture, etc.
2. The basket of permissions that we hit the API endpoint with.

Fusing them together leads to a monstrous solipsism where the AtlasUser cannot imagine any user other than himself. He cannot collaborate with other AtlasUsers unless they give him full access to their APIKeys and authority to hit endpoints with them.

Following practice at Meta, we introduce a new fundamental class called the `AtlasViewer`, which is the agent making requests. `AtlasUser` is just the person in the system. For the time being `AtlasUser` still accepts keys in its constructor, but it passes them through to the underlying AtlasViewer: this behavior will probably be deprecated in version 1.0.

# 0.10.0

# 0.9.6

- Rename "AtlasProject" to "AtlasDataset" with backwards compatible alias.
Expand Down
23 changes: 14 additions & 9 deletions src/embedding.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AtlasViewer } from 'viewer.js';
import { BaseAtlasClass, AtlasUser } from './user.js';

type TaskType =
Expand Down Expand Up @@ -49,7 +50,7 @@ const BATCH_SIZE = 32;
* const embeddings = await embedder.embed(documents)
* ```
*
* GOOD -- Nomic will combine your small requests into several medium-size
* GOOD -- Nomic will combine your small requests into several medium-size ones.
* ```js
* const documents = ["Once upon a time", "there was a girl named Goldilocks", …, "and they all lived happily ever after"]
* const embedder = new Embedder(myApiKey)
Expand All @@ -60,7 +61,7 @@ const BATCH_SIZE = 32;
* const embeddings = await Promise.all(promises)
* ```
*
* BAD -- You will generate many small, inefficient requests
* BAD -- You will generate many small, inefficient requests.
* ```js
* * const documents = ["Once upon a time", "there was a girl named Goldilocks", …, "and they all lived happily ever after"]
* const embedder = new Embedder(myApiKey)
Expand Down Expand Up @@ -98,28 +99,32 @@ export class Embedder extends BaseAtlasClass<{}> {
*/
constructor(apiKey: string, options: EmbedderOptions);
constructor(user: AtlasUser, options: EmbedderOptions);
constructor(input: string | AtlasUser, options: EmbedderOptions = {}) {
constructor(viewer: AtlasViewer, options: EmbedderOptions);
constructor(
input: string | AtlasUser | AtlasViewer,
options: EmbedderOptions = {}
) {
const { model, taskType } = {
// Defaults
model: 'nomic-embed-text-v1.5' as EmbeddingModel,
taskType: 'search_document' as TaskType,
...options,
};
let user: AtlasUser;
let viewer: AtlasViewer | AtlasUser;
if (typeof input === 'string') {
user = new AtlasUser({
viewer = new AtlasViewer({
apiKey: input,
});
} else {
user = input;
viewer = input;
}
// Handle authentication the normal way.
super(user);
super(viewer);
this.model = model;
this.taskType = taskType;
}

endpoint(): string {
protected endpoint(): string {
throw new Error('Embedders do not have info() property.');
}

Expand Down Expand Up @@ -254,7 +259,7 @@ export async function embed(
): Promise<Embedding | Embedding[]> {
const machine =
apiKey === undefined
? new Embedder(new AtlasUser({ useEnvToken: true }), options)
? new Embedder(new AtlasViewer({ useEnvToken: true } as const), options)
: new Embedder(apiKey, options);

if (typeof value === 'string') {
Expand Down
41 changes: 41 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,45 @@ declare namespace Atlas {
};
type Payload = Record<string, any> | Uint8Array | null;
type AtlasUser = {};

type Envlogin = {
useEnvToken: true;
apiLocation?: never;
apiKey?: never;
bearerToken?: never;
};
type ApiKeyLogin = {
useEnvToken?: never;
apiLocation?: string;
apiKey: string;
bearerToken?: never;
};
type BearerTokenLogin = {
useEnvToken?: never;
bearerToken: string;
apiLocation?: string;
apiKey?: never;
};
type AnonViewerLogin = {
useEnvToken?: never;
bearerToken?: never;
apiLocation?: string;
apiKey?: never;
};
type LoginParams =
| Envlogin
| ApiKeyLogin
| BearerTokenLogin
| AnonViewerLogin;

type ApiCallOptions = {
octetStreamAsUint8?: boolean;
};

type TokenRefreshResponse = any;
interface Credentials {
refresh_token: string | null;
token: string;
expires: number;
}
}
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AtlasUser } from './user.js';
import { AtlasProjection } from './projection.js';
import { AtlasDataset as AtlasDataset } from './project.js';
import type { Table } from 'apache-arrow';
import { AtlasViewer } from 'viewer.js';
RLesser marked this conversation as resolved.
Show resolved Hide resolved

type IndexInitializationOptions = {
project_id?: Atlas.UUID;
Expand All @@ -16,7 +17,7 @@ export class AtlasIndex extends BaseAtlasClass<{}> {

constructor(
id: Atlas.UUID,
user?: AtlasUser,
user?: AtlasUser | AtlasViewer,
options: IndexInitializationOptions = {}
) {
super(user);
Expand All @@ -31,7 +32,7 @@ export class AtlasIndex extends BaseAtlasClass<{}> {
this.id = id;
}

endpoint(): string {
protected endpoint(): string {
throw new Error('There is no info property on Atlas Indexes');
}

Expand Down Expand Up @@ -78,7 +79,7 @@ export class AtlasIndex extends BaseAtlasClass<{}> {
?.projections || [];
this._projections = projections.map(
(d) =>
new AtlasProjection(d.id as string, this.user, {
new AtlasProjection(d.id as string, this.viewer, {
index: this,
project: this.project,
})
Expand Down
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { AtlasDataset as AtlasProject } from './project.js';
export { AtlasDataset } from './project.js';
export { AtlasUser, APIError } from './user.js';
export { AtlasUser } from './user.js';
export { APIError, AtlasViewer } from './viewer.js';
export { AtlasOrganization } from './organization.js';
export { AtlasProjection } from './projection.js';
export { AtlasIndex } from './index.js';
Expand Down
9 changes: 4 additions & 5 deletions src/organization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AtlasUser, BaseAtlasClass, get_env_user } from './user.js';
import { AtlasUser, BaseAtlasClass, getEnvViewer } from './user.js';
import { AtlasDataset } from './project.js';

type UUID = string;
Expand Down Expand Up @@ -26,7 +26,7 @@ export class AtlasOrganization extends BaseAtlasClass<OrganizationInfo> {
this.id = id;
}

endpoint() {
protected endpoint() {
return `/v1/organization/${this.id}`;
}

Expand All @@ -36,7 +36,6 @@ export class AtlasOrganization extends BaseAtlasClass<OrganizationInfo> {
}

async create_project(options: ProjectInitOptions): Promise<AtlasDataset> {
const user = this.user;
if (options.unique_id_field === undefined) {
throw new Error('unique_id_field is required');
}
Expand All @@ -47,10 +46,10 @@ export class AtlasOrganization extends BaseAtlasClass<OrganizationInfo> {
type CreateResponse = {
project_id: UUID;
};
const data = (await user.apiCall(`/v1/project/create`, 'POST', {
const data = (await this.apiCall(`/v1/project/create`, 'POST', {
...options,
organization_id: this.id,
})) as CreateResponse;
return new AtlasDataset(data['project_id'], user);
return new AtlasDataset(data['project_id'], this.viewer);
}
}
27 changes: 16 additions & 11 deletions src/project.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Schema, Table } from 'apache-arrow';
import type { ApiCallOptions } from './user.js';
import { tableToIPC, tableFromJSON, tableFromIPC } from 'apache-arrow';
import { AtlasUser, get_env_user, BaseAtlasClass } from './user.js';
import { AtlasUser, BaseAtlasClass } from './user.js';
import { AtlasIndex } from './index.js';
import { AtlasViewer } from 'viewer.js';
// get the API key from the node environment
import { OrganizationProjectInfo } from 'organization.js';
type UUID = string;

export function load_project(options: Atlas.LoadProjectOptions): AtlasDataset {
Expand Down Expand Up @@ -74,7 +73,7 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
*
* @returns An AtlasDataset object.
*/
constructor(id: UUID | string, user?: AtlasUser) {
constructor(id: UUID | string, user?: AtlasUser | AtlasViewer) {
super(user);
// check if id is a valid UUID

Expand Down Expand Up @@ -116,10 +115,16 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
method: 'GET' | 'POST',
payload: Atlas.Payload = null,
headers: null | Record<string, string> = null,
options: ApiCallOptions = {}
options: Atlas.ApiCallOptions = {}
) {
const fixedEndpoint = this._fixEndpointURL(endpoint);
return this.user.apiCall(fixedEndpoint, method, payload, headers, options);
return this.viewer.apiCall(
fixedEndpoint,
method,
payload,
headers,
options
);
}

async delete() {
Expand All @@ -139,7 +144,7 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
// Create a new project to clear the cache.
const renewed = new AtlasDataset(this.id, this.user);
const renewed = new AtlasDataset(this.id, this.viewer);
const info = (await renewed.info()) as Atlas.ProjectInfo;
if (info.insert_update_delete_lock === false) {
clearInterval(interval);
Expand All @@ -151,7 +156,7 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
});
}

endpoint() {
protected endpoint() {
return `/v1/project/${this.id}`;
}

Expand All @@ -176,7 +181,7 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
}
const options = { project: this };
this._indices = atlas_indices.map(
(d) => new AtlasIndex(d['id'], this.user, options)
(d) => new AtlasIndex(d['id'], this.viewer, options)
);
return this._indices;
}
Expand Down Expand Up @@ -268,13 +273,13 @@ export class AtlasDataset extends BaseAtlasClass<Atlas.ProjectInfo> {
fields
);
const id = response as string;
return new AtlasIndex(id, this.user, { project: this });
return new AtlasIndex(id, this.viewer, { project: this });
}

async delete_data(ids: string[]): Promise<void> {
// TODO: untested
// const info = await this.info
await this.user.apiCall('/v1/project/data/delete', 'POST', {
await this.viewer.apiCall('/v1/project/data/delete', 'POST', {
project_id: this.id,
datum_ids: ids,
});
Expand Down
13 changes: 7 additions & 6 deletions src/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BaseAtlasClass } from './user.js';
import type { AtlasUser } from './user.js';
import { AtlasDataset } from './project.js';
import type { AtlasIndex } from './index.js';
import { AtlasViewer } from 'viewer.js';

export type ProjectGetInfo = Record<string, any>;

Expand Down Expand Up @@ -92,11 +93,11 @@ export class AtlasProjection extends BaseAtlasClass<ProjectGetInfo> {

constructor(
public id: UUID,
user?: AtlasUser,
user?: AtlasUser | AtlasViewer,
options: ProjectionInitializationOptions = {}
) {
const { project, project_id } = options;
super(user || project?.user);
super(user || project?.viewer);

if (project_id === undefined && project === undefined) {
throw new Error('project_id or project is required');
Expand Down Expand Up @@ -266,7 +267,7 @@ export class AtlasProjection extends BaseAtlasClass<ProjectGetInfo> {

async project(): Promise<AtlasDataset> {
if (this._project === undefined) {
this._project = new AtlasDataset(this.project_id, this.user);
this._project = new AtlasDataset(this.project_id, this.viewer);
}
return this._project;
}
Expand Down Expand Up @@ -297,13 +298,13 @@ export class AtlasProjection extends BaseAtlasClass<ProjectGetInfo> {
* 'public' may be be added in fetching.
*/
get quadtree_root(): string {
const protocol = this.user.apiLocation.startsWith('localhost')
const protocol = this.viewer.apiLocation.startsWith('localhost')
? 'http'
: 'https';
return `${protocol}://${this.user.apiLocation}/v1/project/${this.project_id}/index/projection/${this.id}/quadtree`;
return `${protocol}://${this.viewer.apiLocation}/v1/project/${this.project_id}/index/projection/${this.id}/quadtree`;
}

endpoint() {
protected endpoint() {
return `/v1/project/${this.project_id}/index/projection/${this.id}`;
}
}
Loading