From e67639ba699f696bd75a256a8acb1fe2ab8d5bf1 Mon Sep 17 00:00:00 2001 From: Timmy Jose Date: Tue, 22 Aug 2023 14:04:51 +0530 Subject: [PATCH 1/5] [WIp] Adding documentatioon for @polybase/client: - added minimal support for TSDoc --- .gitignore | 3 ++- packages/client/package.json | 3 ++- packages/client/typedoc.json | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 packages/client/typedoc.json diff --git a/.gitignore b/.gitignore index 301a6f1..50fb9e4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ dist node web .log -.npmrc \ No newline at end of file +.npmrc +**/doc \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json index c62f4db..e402609 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -17,7 +17,8 @@ "release": "npx np", "test": "jest ./src", "test:e2e": "jest ./e2e --verbose", - "fix": "yarn eslint \"./src/**/*.{ts,tsx}\" --fix" + "fix": "yarn eslint \"./src/**/*.{ts,tsx}\" --fix", + "doc": "npx typedoc" }, "jest": { "preset": "ts-jest", diff --git a/packages/client/typedoc.json b/packages/client/typedoc.json new file mode 100644 index 0000000..a160d81 --- /dev/null +++ b/packages/client/typedoc.json @@ -0,0 +1,5 @@ +{ + "entryPoints": ["src"], + "entryPointStrategy": "expand", + "out": "./doc" +} \ No newline at end of file From 5bbd585901ca4b1ecb07fdb8fd776fdf9ea6f39f Mon Sep 17 00:00:00 2001 From: Timmy Jose Date: Tue, 22 Aug 2023 14:17:04 +0530 Subject: [PATCH 2/5] - Added `typedoc` to `package.json` - Updated README --- packages/client/README.md | 16 ++++++++++++++++ packages/client/package.json | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/client/README.md b/packages/client/README.md index a51eca9..54e4c09 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -49,3 +49,19 @@ To run E2E tests, ensure that [Polybase](https://github.com/polybase/polybase) i ``` yarn test:e2e ``` + +# Doc + +To generate the documentation for the project, run the following command: + +``` + $ npm run doc +``` + +or + +``` + $ yarn run doc +``` + +This will generate the documentation in the `doc` directory. diff --git a/packages/client/package.json b/packages/client/package.json index e402609..cae73c9 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -49,7 +49,8 @@ "ts-loader": "^9.4.2", "typescript": "^4.9.4", "webpack": "^5.75.0", - "webpack-cli": "^5.0.1" + "webpack-cli": "^5.0.1", + "typedoc": "^0.24.8" }, "dependencies": { "@polybase/eth": "^0.6.5", From e6c426ddcc28569fbbf8656644310777c43626fc Mon Sep 17 00:00:00 2001 From: Timmy Jose Date: Tue, 22 Aug 2023 16:23:05 +0530 Subject: [PATCH 3/5] Adding documentation to @polybase/client code: - added a new plugin to export non-exported (but referenced) types (typedoc-plugin-missing-exports) - Added basic documentation for `Polybase` and `Client` types. --- packages/client/package.json | 7 ++++--- packages/client/src/Client.ts | 29 ++++++++++++++++++++++++++- packages/client/src/Polybase.ts | 35 ++++++++++++++++++++++++++++++++- packages/client/typedoc.json | 6 +++++- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index cae73c9..41714dd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -18,7 +18,7 @@ "test": "jest ./src", "test:e2e": "jest ./e2e --verbose", "fix": "yarn eslint \"./src/**/*.{ts,tsx}\" --fix", - "doc": "npx typedoc" + "doc": "npx typedoc --plugin typedoc-plugin-missing-exports" }, "jest": { "preset": "ts-jest", @@ -47,10 +47,11 @@ "terser-webpack-plugin": "^5.3.6", "ts-jest": "^29.1.0", "ts-loader": "^9.4.2", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.1", "typescript": "^4.9.4", "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", - "typedoc": "^0.24.8" + "webpack-cli": "^5.0.1" }, "dependencies": { "@polybase/eth": "^0.6.5", diff --git a/packages/client/src/Client.ts b/packages/client/src/Client.ts index cfb8793..9f5f864 100644 --- a/packages/client/src/Client.ts +++ b/packages/client/src/Client.ts @@ -3,13 +3,31 @@ import { AxiosError, AxiosRequestConfig } from 'axios' import { createError, createErrorFromAxiosError } from './errors' import { QueryValue, Request, RequestParams, Sender, SenderResponse, Signer, SignerResponse } from './types' +/** + * The Client configuration. + */ export interface ClientConfig { + /** The unique identifier for this client. + * @example + * ``` + * polybase@ts/client:v0 + * ``` + */ clientId: string + /** The base URL of the Polybase service. + * @example + * ``` + * https://testnet.polybase.xyz/v0 + * ``` + */ baseURL: string } type SignatureCache = Record +/** + * The Client used by the Polybase Client for interacting with the Polybase service. + */ export class Client { private sender: Sender signer?: Signer @@ -23,6 +41,9 @@ export class Client { this.signatureCache = {} } + /** + * Returns a new {@link ClientRequest} instance. + */ request = (req: Request): ClientRequest => { return new ClientRequest(this.sender, { url: req.url, @@ -54,7 +75,12 @@ export class ClientRequest { this.aborter.abort() } - /* Sending a request to the server. */ + /** + * Send a request to the server. + * + * @param withAuth - The authentication mode. + * @param sigExtraTimeMs - Extra time to sign the request. + */ send = async (withAuth: 'none' | 'optional' | 'required', sigExtraTimeMs?: number): Promise> => { try { const req = this.req as AxiosRequestConfig @@ -99,6 +125,7 @@ export class ClientRequest { } } + /** @private */ private getSignature = async (extraTimeMs: number) => { if (!this.signer) return '' diff --git a/packages/client/src/Polybase.ts b/packages/client/src/Polybase.ts index eef669d..ac4ff4b 100644 --- a/packages/client/src/Polybase.ts +++ b/packages/client/src/Polybase.ts @@ -5,9 +5,33 @@ import { Collection } from './Collection' import { PolybaseError, createError } from './errors' import { CollectionMeta, Sender, Signer } from './types' +/** + * Configuration for the Polybase Client. + */ export interface PolybaseConfig { + /** + * The baseURL of the Polybase service. + * @example + * ``` + * https://testnet.polybase.xyz/v0 + * ``` + */ baseURL: string + /** + * The unique identifier of the Client. + * @example + * ``` + * polybase@ts/client:v0 + * ``` + */ clientId: string + /** + * The default namespace for the Client. + * @example + * ``` + * "pk/0x1fda4bead8bc2e85d4de75694b893de5dfb0fbe69e8ed1d2531c805db483ba350ea28d4b1c7acf6171d902586e439a04f23cb0827b08a29cbdf3dd0e5c994ec0/MyClient" + * ``` + */ defaultNamespace?: string sender: Sender signer?: Signer @@ -19,6 +43,9 @@ const defaultConfig = { sender: axios, } +/** + * The Polybase Client. + */ export class Polybase { private config: PolybaseConfig private client: Client @@ -71,7 +98,13 @@ export class Polybase { return this.collection(data.id) } - /* Applies the given schema to the database, creating new collections and adding existing collections */ + /** + * Applies the given schema to the database, creating new collections and adding existing collections. + * + * @param schema: The schema to apply. + * @param namespace: The namespace for the collection. + * @returns An array of collections. + */ applySchema = async (schema: string, namespace?: string): Promise[]> => { const collections = [] const ns = (namespace ?? this.config.defaultNamespace) diff --git a/packages/client/typedoc.json b/packages/client/typedoc.json index a160d81..e714f8e 100644 --- a/packages/client/typedoc.json +++ b/packages/client/typedoc.json @@ -1,5 +1,9 @@ { - "entryPoints": ["src"], + "entryPoints": [ + "src" + ], "entryPointStrategy": "expand", + "excludeExternals": true, + "excludePrivate": false, "out": "./doc" } \ No newline at end of file From 9ff786c08373ba0a0c3ad779b1e6999031d8ea05 Mon Sep 17 00:00:00 2001 From: Timmy Jose Date: Tue, 22 Aug 2023 17:52:19 +0530 Subject: [PATCH 4/5] Changes: - added documentation to functions in `util.ts`. - added documentation for `Record`. --- packages/client/src/Client.ts | 3 ++ packages/client/src/Polybase.ts | 18 ++++++- packages/client/src/Query.ts | 7 +++ packages/client/src/Record.ts | 34 ++++++++++++- packages/client/src/Subscription.ts | 8 ++- packages/client/src/util.ts | 75 +++++++++++++++++++++++++++-- 6 files changed, 138 insertions(+), 7 deletions(-) diff --git a/packages/client/src/Client.ts b/packages/client/src/Client.ts index 9f5f864..67bf2a3 100644 --- a/packages/client/src/Client.ts +++ b/packages/client/src/Client.ts @@ -165,6 +165,9 @@ export class ClientRequest { } } +/** + * Parse the given request parameters into a {@link Record} object. + */ export function parseParams(params?: RequestParams): Record { if (!params) return {} return { diff --git a/packages/client/src/Polybase.ts b/packages/client/src/Polybase.ts index ac4ff4b..4f4c0c8 100644 --- a/packages/client/src/Polybase.ts +++ b/packages/client/src/Polybase.ts @@ -1,3 +1,13 @@ +/** + *

Defines the types and values of the Polybase Client SDK.

+ * + *

The Polybase module is how we communicate with the Polybase service. + * @see [Getting Started](https://polybase.xyz/docs/get-started) + *

+ * + * @module + */ + import { parse } from '@polybase/polylang' import axios from 'axios' import { Client } from './Client' @@ -61,6 +71,12 @@ export class Polybase { ) } + /** + * Retrieve the collection with the given path. + * + * @param path - the fully-qualified path to the collection. + * @returns The given {@link Collection} instance. + */ collection(path: string): Collection { const rp = this.getResolvedPath(path) if (this.collections[rp]) return this.collections[rp] @@ -103,7 +119,7 @@ export class Polybase { * * @param schema: The schema to apply. * @param namespace: The namespace for the collection. - * @returns An array of collections. + * @returns An array of {@link Collection} instances. */ applySchema = async (schema: string, namespace?: string): Promise[]> => { const collections = [] diff --git a/packages/client/src/Query.ts b/packages/client/src/Query.ts index 60f3e71..6789d1f 100644 --- a/packages/client/src/Query.ts +++ b/packages/client/src/Query.ts @@ -1,3 +1,10 @@ +/** + * + * @module + * @see [Filter records](https://polybase.xyz/docs/read#filter-records) + * @see [Pagination](https://polybase.xyz/docs/read#pagination) + */ + import { Client } from './Client' import { Collection, QuerySnapshotRegister } from './Collection' import { CollectionRecord, CollectionRecordResponse } from './Record' diff --git a/packages/client/src/Record.ts b/packages/client/src/Record.ts index 88ddfc8..db47f6a 100644 --- a/packages/client/src/Record.ts +++ b/packages/client/src/Record.ts @@ -1,3 +1,9 @@ +/** + * This module contains types and functions for the actual instances of collections. + * + * @module + */ + import { Collection } from './Collection' import { SubscriptionErrorFn, SubscriptionFn } from './Subscription' import { CollectionRecordSnapshotRegister, Request, CallArgs, SenderRawRecordResponse, Block } from './types' @@ -11,6 +17,9 @@ export type CollectionRecordReference = { id: string } +/** + * This represents an instance of a collection. + */ export class CollectionRecord { id: string private collection: Collection @@ -24,6 +33,13 @@ export class CollectionRecord { this.onSnapshotRegister = onSnapshotRegister } + /** + * Call a function on this collection record. + * The function must be a custom function defined in the collection schema. + * + * @see [Write Data](https://polybase.xyz/docs/write-data) + * @see [Delete Data](https://polybase.xyz/docs/delete-data) + */ call = async (functionName: string, args: CallArgs = []): Promise> => { const ast = await this.collection.getAST() const isCallPubliclyAccessible = await this.collection.isCallPubliclyAccessible(functionName) @@ -39,6 +55,11 @@ export class CollectionRecord { return new CollectionRecordResponse(this.id, res.data, ast, this.collection, this.client, this.onSnapshotRegister) } + /** + * Retrieve the collection record. + * + * @see [Read Data](https://polybase.xyz/docs/read) + */ get = async (): Promise> => { const ast = await this.collection.getAST() const isReadPubliclyAccessible = await this.collection.isReadPubliclyAccessible() @@ -65,16 +86,27 @@ export class CollectionRecord { return `record:${this.collection.id}/${this.id}` } + /** + * Listener for updates to this record (after the write is confirmed). + * + * @see [Listen for updates on a record](https://polybase.xyz/docs/read#listen-for-updates-on-a-record) + */ onSnapshot = (fn: SubscriptionFn>, errFn?: SubscriptionErrorFn) => { return this.onSnapshotRegister(this, fn, errFn) } + /** + * The {@link Request} object for this collection record. + */ request = (): Request => ({ url: `/collections/${encodeURIComponent(this.collection.id)}/records/${encodeURIComponent(this.id)}`, method: 'GET', }) } +/** + * The collection record data, as returned by the Polybase service. + */ export class CollectionRecordResponse extends CollectionRecord { data: NT block: Block @@ -100,6 +132,6 @@ export class CollectionRecordResponse extends Collec } /** - * @deprecated use CollectionRecord + * @deprecated use CollectionRecord instead */ export const Doc = CollectionRecord diff --git a/packages/client/src/Subscription.ts b/packages/client/src/Subscription.ts index 87097a9..2f9bf80 100644 --- a/packages/client/src/Subscription.ts +++ b/packages/client/src/Subscription.ts @@ -7,9 +7,13 @@ export type SubscriptionErrorFn = ((err: PolybaseError) => void) export type UnsubscribeFn = (() => void) export interface SubscriptionOptions { - // Default timeout between long poll requests + /** + * Default timeout between long poll requests. + */ timeout: number - // Max timeout after error backoff + /** + * Max timeout after error backoff. + */ maxErrorTimeout: number } diff --git a/packages/client/src/util.ts b/packages/client/src/util.ts index 3032bd8..cc64524 100644 --- a/packages/client/src/util.ts +++ b/packages/client/src/util.ts @@ -1,11 +1,31 @@ +/** + * Utiltiy functions for use by other modules of @polybase/client. + * + * @module + */ + import { CollectionRecord } from './Record' import type { CallArg, FieldTypes } from './types' import { Root as AST, Property as ASTProperty, Collection as ASTCollection, CollectionAttribute, ObjectField } from '@polybase/polylang/dist/ast' +/** + * Retrieve the Collection AST for the given id and ast. + * + * @param id - the collection id. + * @param ast - the ast representing the schema of the collection. + * @returns The prepared Collection AST. + */ export function getCollectionASTFromId(id: string, ast: AST): ASTCollection | undefined { return getCollectionASTFromName(getCollectionShortNameFromId(id), ast) } +/** + * Retrieve the Collection AST for the given name and ast. + * + * @param name - the collection name. + * @param ast - the ast representing the schema of the collection. + * @returns The prepared Collection AST. + */ export function getCollectionASTFromName(name: string, ast: AST): ASTCollection | undefined { const collections = ast.filter((n) => n.kind === 'collection') as ASTCollection[] return collections.find((n: ASTCollection) => n.name === name) @@ -15,16 +35,52 @@ export function getCollectionProperties(collection: ASTCollection): ASTProperty[ return collection.attributes.filter((a: CollectionAttribute) => a.kind === 'property') as ASTProperty[] } +/** + * Extracts the short collection name by removing the namespace prefix. + * + * @example + * ```ts + * const shortName = 'ns/foo' + * console.log(shortName) // displays 'foo' + * ``` + * @param id - the collection id. + * @returns The short collection name. + */ export function getCollectionShortNameFromId(id: string): string { const name = id.split('/').pop() if (!name) throw new Error(`Invalid collection id: ${id}`) return name } +/** + * Encodes the given byte array into a Base64 encoded string. + * + * @example + * ```ts + * const text = 'Hello, world' + * const uint8Array = new Uint8Array(new TextEncoder().encode(text)) + * const base64Encoded = encodeBase64(uint8Array) + * console.log(base64Encoded) // displays 'SGVsbG8sIHdvcmxk' + * ``` + * @param value - the byte arrey. + * @returns The Base64 encoded string. + */ export function encodeBase64(value: Uint8Array): string { return btoa(String.fromCharCode.apply(null, value as unknown as number[])) } +/** + * Decodes the given Base64 encoded string into a byte array. + * + * @example + * ```ts + * const base64Encoded = 'SGVsbG8sIHdvcmxk' + * const decoder = new TextDecoder() + * console.log(decoder.decode(decodeBase64(base64Encoded)) // displays 'Hello, world' + * ``` + * @param value - the byte arrey. + * @returns The Base64 encoded string. + */ export function decodeBase64(value: string): Uint8Array { const binaryString = atob(value) @@ -74,14 +130,25 @@ export function deserializeRecord(data: Record, properties: (ASTPro } } -// Adds a key/value to a record, or creates an object with the key/value +/** + * Adds a key/value to a record, or creates an object with the key/value. + * + * @param key - the entry key. + * @param value - the entry value. + * @returns The updated (or newly-created) record. + */ export function addKeyValue(key: string, value: any, obj?: Record): Record { const o = obj ?? {} o[key] = value return o } -// Removes given keys from the object +/** + * Removes the given keys from the object, if the keys are present in the record. + * + * @param keys - the keys to remove. + * @returns The updated record with the keys removed. + */ export function removeKey(key: string, obj?: any): Record { if (!obj || !isPlainObject(obj)) return {} for (const k of key) { @@ -92,7 +159,9 @@ export function removeKey(key: string, obj?: any): Record { return obj } -// Returns true if the object is a plain object +/** + * Returns true if the object is a plain object. + */ export function isPlainObject(val: any): val is Record { return typeof val === 'object' && val.constructor === Object } From 0c1b9c61cfacdac96b62d0be8cab24a8e23ff564 Mon Sep 17 00:00:00 2001 From: Timmy Jose Date: Tue, 22 Aug 2023 20:47:55 +0530 Subject: [PATCH 5/5] Minor changes: - using tenses consistently. - added some links to the official docs in Collection. --- packages/client/src/Collection.ts | 41 +++++++++++++++++++++++++++++++ packages/client/src/Polybase.ts | 4 +-- packages/client/src/Query.ts | 1 - packages/client/src/Record.ts | 5 ++-- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/client/src/Collection.ts b/packages/client/src/Collection.ts index f6945c9..e66252a 100644 --- a/packages/client/src/Collection.ts +++ b/packages/client/src/Collection.ts @@ -1,3 +1,8 @@ +/** + * @see [Collections](https://polybase.xyz/docs/collections) + * @module + */ + import { CollectionRecord, CollectionRecordResponse } from './Record' import { Query, QueryResponse } from './Query' import { Subscription, SubscriptionFn, SubscriptionErrorFn, UnsubscribeFn } from './Subscription' @@ -29,6 +34,9 @@ export class Collection { load = async () => { } + /** + * returns The collection metadata. + */ getMeta = async (): Promise => { if (this.meta) return this.meta // Manually get Collection meta, otherwise we would recursively call this function @@ -44,6 +52,9 @@ export class Collection { return this.meta } + /** + * @returns The prepared AST for this collection. + */ getAST = async (): Promise => { // Return cached value if it exists if (this.astCache) return this.astCache @@ -55,10 +66,19 @@ export class Collection { return collectionAST } + /** + * @returns The short name for this collection. + */ name(): string { return getCollectionShortNameFromId(this.id) } + /** + * Validate the given collection ast. + * + * @param data - the data to be validated + * @returns Whether validation succeeded (`true`) or not (`false`). + */ validate = async (data: Partial) => { const ast = await this.getAST() try { @@ -69,6 +89,10 @@ export class Collection { } } + /** + * @returns Whether this collection can be read pubclicly (`true) or not (`false`) + * @see [Permissions](https://polybase.xyz/docs/permissions) + */ isReadPubliclyAccessible = async (): Promise => { // Without this, we would recursively call this function if (this.id === 'Collection') return true @@ -76,6 +100,11 @@ export class Collection { return this.isCollectionPubliclyAccessible('read') } + /** + * @param methodName - the method (as defined in the collection schema). + * @returns Whether the method can be called (`true`) on this collection or not (`false`). + * @see [Permissions](https://polybase.xyz/docs/permissions) + */ isCallPubliclyAccessible = async (methodName: string) => { // Without this, we would recursively call this function if (this.id === 'Collection') return true @@ -104,6 +133,12 @@ export class Collection { return hasPublicDirective || hasTypeDirective } + /** + * Create a collection record. + * + * @param args - the data for the new collection. + * @returns The newly created collection record. + */ create = async (args: CallArgs = []): Promise> => { if (!Array.isArray(args)) { throw new TypeError('invalid argument: `args` must be an array') @@ -127,6 +162,12 @@ export class Collection { return this.createQuery().get() } + /** + * Retrieves the record, for this collection, with the given id. + * + * @param id - the id of the collection record.:w + * @returns The collection record. + */ record = (id: string): CollectionRecord => { return new CollectionRecord(id, this, this.client, this.onRecordSnapshotRegister) } diff --git a/packages/client/src/Polybase.ts b/packages/client/src/Polybase.ts index 4f4c0c8..1345263 100644 --- a/packages/client/src/Polybase.ts +++ b/packages/client/src/Polybase.ts @@ -1,5 +1,5 @@ /** - *

Defines the types and values of the Polybase Client SDK.

+ *

The Polybase Client SDK.

* *

The Polybase module is how we communicate with the Polybase service. * @see [Getting Started](https://polybase.xyz/docs/get-started) @@ -72,7 +72,7 @@ export class Polybase { } /** - * Retrieve the collection with the given path. + * Retrieves the collection with the given path. * * @param path - the fully-qualified path to the collection. * @returns The given {@link Collection} instance. diff --git a/packages/client/src/Query.ts b/packages/client/src/Query.ts index 6789d1f..767fcec 100644 --- a/packages/client/src/Query.ts +++ b/packages/client/src/Query.ts @@ -1,5 +1,4 @@ /** - * * @module * @see [Filter records](https://polybase.xyz/docs/read#filter-records) * @see [Pagination](https://polybase.xyz/docs/read#pagination) diff --git a/packages/client/src/Record.ts b/packages/client/src/Record.ts index db47f6a..50b28fd 100644 --- a/packages/client/src/Record.ts +++ b/packages/client/src/Record.ts @@ -34,7 +34,7 @@ export class CollectionRecord { } /** - * Call a function on this collection record. + * Calls a function on this collection record. * The function must be a custom function defined in the collection schema. * * @see [Write Data](https://polybase.xyz/docs/write-data) @@ -56,8 +56,9 @@ export class CollectionRecord { } /** - * Retrieve the collection record. + * Retrieves the collection record. * + * @returns The collection recod. * @see [Read Data](https://polybase.xyz/docs/read) */ get = async (): Promise> => {