Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/workflows/authzed-node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
with:
working-directory: ./js-dist
- name: Run Yarn tests
run: CI=true only-run-tests
run: CI=true yarn only-run-tests
working-directory: ./js-dist
- uses: actions/upload-artifact@v2
with:
Expand Down
12 changes: 12 additions & 0 deletions src/v1-promise.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ describe('Lookup APIs', () => {
}),
permission: 'view',
subjectObjectType: 'test/user',
consistency: Consistency.create({
requirement: {
oneofKind: 'fullyConsistent',
fullyConsistent: true,
},
}),
});

const result = await client.lookupSubjects(request)
Expand All @@ -219,6 +225,12 @@ describe('Lookup APIs', () => {
}),
permission: 'view',
resourceObjectType: 'test/document',
consistency: Consistency.create({
requirement: {
oneofKind: 'fullyConsistent',
fullyConsistent: true,
},
}),
});

const resStream = await client.lookupResources(request)
Expand Down
12 changes: 12 additions & 0 deletions src/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ describe('Lookup APIs', () => {
}),
permission: 'view',
subjectObjectType: 'test/user',
consistency: Consistency.create({
requirement: {
oneofKind: 'fullyConsistent',
fullyConsistent: true,
},
}),
});

const resStream = client.lookupSubjects(request);
Expand Down Expand Up @@ -280,6 +286,12 @@ describe('Lookup APIs', () => {
}),
permission: 'view',
resourceObjectType: 'test/document',
consistency: Consistency.create({
requirement: {
oneofKind: 'fullyConsistent',
fullyConsistent: true,
},
}),
});

const resStream = client.lookupResources(request);
Expand Down
206 changes: 118 additions & 88 deletions src/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,117 @@ type ZedPromiseClientInterface = {

type ICombinedClient = ZedClientInterface & { promises: ZedPromiseClientInterface}

const streamMethods = new Set([
'readRelationships',
'lookupResources',
'lookupSubjects'
])
/**
* A standard client_grpc1 (via @grpc/grpc-js) that will correctly
* proxy the namespaced methods to the correct service client.
*/
class ZedClient implements ProxyHandler<ZedClientInterface> {
private acl: PermissionsServiceClient
private ns: SchemaServiceClient
private watch: WatchServiceClient

constructor(endpoint: string, creds: grpc.ChannelCredentials) {
this.acl = new PermissionsServiceClient(endpoint, creds);
this.ns = new SchemaServiceClient(endpoint, creds);
this.watch = new WatchServiceClient(endpoint, creds);
}

static create(endpoint: string, creds: grpc.ChannelCredentials) {
return new Proxy({} as any, new ZedClient(endpoint, creds))
}

get(_target: object, name: string | symbol) {
if ((this.acl as any)[name as string]) {
return (this.acl as any)[name as string];
}

if ((this.ns as any)[name as string]) {
return (this.ns as any)[name as string];
}

if ((this.watch as any)[name as string]) {
return (this.watch as any)[name as string];
}

return undefined;
}
}

/**
* Proxies all methods from the {@link ZedClientInterface} to return promises
* in order to support async/await for {@link ClientUnaryCall} and {@link ClientReadableStream}
* responses. Methods that normally return an instance of stream, will instead return an
* array of objects collected while the stream was open.
*
* @param client The default grpc1 client
* @returns A promise-wrapped grpc1 client
*/
class ZedPromiseClient implements ProxyHandler<ZedPromiseClientInterface>{
private client: ZedClientInterface
private promiseCache: Record<string, any> = {}
private streamMethods = new Set([
'readRelationships',
'lookupResources',
'lookupSubjects'
])

constructor(client: ZedClientInterface) {
this.client = client
}

static create(client: ZedClientInterface) {
return new Proxy({} as any, new ZedPromiseClient(client))
}

get(_target: Record<string, any>, name: string | symbol) {
if (!(name in this.promiseCache)) {
const clientMethod = (this.client as any)[name as string]
if (clientMethod !== undefined) {
if (this.streamMethods.has(name as string)) {
this.promiseCache[name as string] = promisifyStream(clientMethod, this.client)
} else if (typeof clientMethod === 'function') {
this.promiseCache[name as string] = promisify((this.client as any)[name as string]).bind(this.client)
} else {
return clientMethod
}
}
}

return this.promiseCache[name as string]
}
}

/**
* The {@link ZedCombinedClient} proxies both callback/promise-style methods to the underlying
* {@link ZedClient} and {@link ZedPromiseClient} instances. Direct method calls on the combined
* client will result in calling the underlying callback methods (the generated gRPC methods) while
* the same methods accessed at a sub-path `.promises.<method>` will result in the promise-wrapped
* methods.
*/
class ZedCombinedClient implements ProxyHandler<ZedCombinedClient>{
private client: ZedClientInterface
private promiseClient: ZedPromiseClientInterface

constructor(client: ZedClientInterface, promiseClient: ZedPromiseClientInterface) {
this.client = client
this.promiseClient = promiseClient
}

static create(endpoint: string, creds: grpc.ChannelCredentials) {
const client = ZedClient.create(endpoint, creds)
const promiseClient = ZedPromiseClient.create(client)

return new Proxy({} as any, new ZedCombinedClient(client, promiseClient))
}

get(_target: object, name: string | symbol) {
if (name === 'promises') {
return this.promiseClient
}

return (this.client as any)[name as string]
}
}

/**
* NewClient creates a new client for calling Authzed APIs.
Expand Down Expand Up @@ -68,10 +174,12 @@ export function NewClientWithCustomCert(
/**
* NewClientWithChannelCredentials creates a new client for calling Authzed APIs using custom grpc ChannelCredentials.
*
* The combined client exposes all service-level methods at the root which directly call grpc-generated methods
* while also exposing a `promises` object containing all promise-wrapped methods. For all methods that
* return a {@link ClientReadableStream}, the promise-wrapped method will return an array of the resulting responses
* after the stream has been closed.
The {@link ZedCombinedClient} proxies both callback/promise-style methods to the underlying
* {@link ZedClient} and {@link ZedPromiseClient} instances. Direct method calls on the combined
* client will result in calling the underlying callback methods (the generated gRPC methods) while
* the same methods accessed at a sub-path `.promises.<method>` will result in the promise-wrapped
* methods. For all methods that return a {@link ClientReadableStream}, the promise-wrapped method
* will return an array of the resulting responses after the stream has been closed.
*
* @param endpoint Uri for communicating with Authzed.
* @param creds ChannelCredentials used for grpc.
Expand All @@ -81,85 +189,7 @@ export function NewClientWithChannelCredentials(
endpoint = util.authzedEndpoint,
creds: grpc.ChannelCredentials
): ICombinedClient {
const proxy = createClient(endpoint, creds)
const promiseClient = createPromiseClient(proxy)

const fullHandler = {
get(target: object, name: string | symbol) {
if (name === 'promises') {
return promiseClient
}

return (proxy as any)[name as string]
}
}

return new Proxy({}, fullHandler) as ICombinedClient
}

/**
* Creates the standard client_grpc1 (via @grpc/grpc-js) that will correctly
* proxy the namespaced methods to the correct service client.
*
* @param endpoint The grpc endpoing
* @param creds The channel credentials
* @returns A default grpc1 client
*/
function createClient(endpoint: string, creds: grpc.ChannelCredentials): ZedClientInterface {
const acl = new PermissionsServiceClient(endpoint, creds);
const ns = new SchemaServiceClient(endpoint, creds);
const watch = new WatchServiceClient(endpoint, creds);


const handler = {
get(target: object, name: string | symbol) {
if ((acl as any)[name as string]) {
return (acl as any)[name as string];
}

if ((ns as any)[name as string]) {
return (ns as any)[name as string];
}

if ((watch as any)[name as string]) {
return (watch as any)[name as string];
}

return undefined;
},
};

return new Proxy<ZedClientInterface>({} as ZedClientInterface, handler)
}

/**
* Proxies all methods from the {@link ZedClientInterface} to return promises
* in order to support async/await for {@link ClientUnaryCall} and {@link ClientReadableStream}
* responses.
*
* @param client The default grpc1 client
* @returns A promise-wrapped grpc1 client
*/
function createPromiseClient(client: ZedClientInterface): ZedPromiseClientInterface {
const handler = {
get(target: object, name: string | symbol) {
if ((client as any)[name as string]) {
if (streamMethods.has(name as string)) {
return promisifyStream((client as any)[name as string], client)
}

if (typeof (client as any)[name as string] === 'function') {
return promisify((client as any)[name as string]).bind(client);
}

return (client as any)[name as string]
}

return undefined
},
};

return new Proxy<ZedPromiseClientInterface>({} as any, handler);
return ZedCombinedClient.create(endpoint, creds)
}

export * from "./authzedapi/authzed/api/v1/core";
Expand Down