-
Notifications
You must be signed in to change notification settings - Fork 1
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
(sdk) add helper to create studio project aliases #48
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { api as studioApi } from "@tableland/studio-client"; | ||
import { type WaitableTransactionReceipt } from "../registry/utils.js"; | ||
import { type FetchConfig } from "../validator/client/index.js"; | ||
import { type ChainName, getBaseUrl } from "./chains.js"; | ||
|
@@ -116,6 +117,40 @@ export function jsonFileAliases(filepath: string): AliasesNameMap { | |
}; | ||
} | ||
|
||
// NOTE: In the future we may need to use `environmentId` instead of `projectId`, but | ||
// there is currently no concept of an environment for a user, and the api doesn't | ||
// support querying for deployments based on environment. | ||
export function studioAliases( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the meat of the feature. The studio project name map is loaded and used for read and insert only. |
||
projectId: string, | ||
apiUrl?: string | ||
): AliasesNameMap { | ||
const api = studioApi({ | ||
url: apiUrl, | ||
}); | ||
const loadMap = async function (): Promise<void> { | ||
const res = await api.deployments.projectDeployments.query({ projectId }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only works now because there is a single environment called The root of this problem is using If we do that, we can create a new api function to return deployments for an environment id, then the map will be accurate. |
||
|
||
_map = {}; | ||
// map the response to a `NameMapping` Object | ||
// { tokenId: string; tableId: string; tableName: string; environmentId: string; chainId: number; blockNumber: number | null; txnHash: string | null; createdAt: string; } | ||
res.forEach(function (row) { | ||
_map[row.tableName.split("_").slice(0, -2).join("_")] = row.tableName; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not quite right. You're keying the map based on the Tableland table name. We want to key it on the Studio Table name in the Blueprint. It is true that these should be the same, but if we introduce the ability to rename a Studio Table, they will be different. They can also be different for imported tables because the user chooses the Studio table name for the imported Tableland table. This can be easily addressed when we implement the |
||
}); | ||
}; | ||
|
||
let _map: NameMapping; | ||
return { | ||
read: async function (): Promise<NameMapping> { | ||
if (typeof _map === "undefined") await loadMap(); | ||
|
||
return _map; | ||
}, | ||
write: async function () { | ||
throw new Error("cannot create project tables via studio sdk aliases"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The write function may never be used since creating tables must be done via Studio There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect. |
||
}, | ||
}; | ||
} | ||
|
||
export function prepReadConfig(config: Partial<ReadConfig>): FetchConfig { | ||
const conf: FetchConfig = {}; | ||
if (config.apiKey) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,15 @@ import url from "node:url"; | |
import path from "node:path"; | ||
import fs from "node:fs"; | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import { strictEqual, rejects } from "assert"; | ||
import { deepStrictEqual, equal, strictEqual, rejects } from "assert"; | ||
import { describe, test } from "mocha"; | ||
import { Wallet } from "ethers"; | ||
import { getAccounts } from "@tableland/local"; | ||
import { | ||
type NameMapping, | ||
getDefaultProvider, | ||
jsonFileAliases, | ||
studioAliases, | ||
} from "../src/helpers/index.js"; | ||
import { Database } from "../src/index.js"; | ||
import { TEST_TIMEOUT_FACTOR, TEST_PROVIDER_URL } from "./setup"; | ||
|
@@ -17,7 +19,7 @@ import { TEST_TIMEOUT_FACTOR, TEST_PROVIDER_URL } from "./setup"; | |
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); | ||
|
||
describe("aliases", function () { | ||
this.timeout(TEST_TIMEOUT_FACTOR * 10000); | ||
this.timeout(TEST_TIMEOUT_FACTOR * 30000); | ||
// Note that we're using the second account here | ||
const [, wallet] = getAccounts(); | ||
const provider = getDefaultProvider(TEST_PROVIDER_URL); | ||
|
@@ -232,4 +234,76 @@ describe("aliases", function () { | |
strictEqual(nameMap[tablePrefix], uuTableName); | ||
}); | ||
}); | ||
|
||
describe.skip("studio based aliases", function () { | ||
// TODO: these values are set per the actual production studio. It's tempting | ||
// to sandbox this, but doing so would add significant complexity. | ||
// TODO: move the argument values to env vars. | ||
const aliases = studioAliases( | ||
"6f254b66-d9cf-482b-a4b9-76cfe5eb2f19", | ||
"https://studio-neon.vercel.app/" | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. usage with the SDK is fairly straight forward. Just provide a projectId and the URL of the studio. We can remove the requirement for studio url once we have a permanent url. |
||
|
||
const wallet = new Wallet(process.env.PRIVATE_KEY as string); | ||
const provider = getDefaultProvider(process.env.PROVIDER_URL as string); | ||
const signer = wallet.connect(provider); | ||
const db = new Database({ | ||
signer, | ||
aliases, | ||
autoWait: true, | ||
}); | ||
|
||
const getNextId = async function (): Promise<number> { | ||
const res = await db | ||
.prepare("select * from users ORDER BY id DESC LIMIT 1;") | ||
.all(); | ||
|
||
// @ts-expect-error TODO | ||
const nextId = res.results[0]?.id; | ||
|
||
if (typeof nextId !== "number" || isNaN(nextId)) { | ||
return 0; | ||
} | ||
|
||
return nextId; | ||
}; | ||
|
||
test("can use the production studio to do reads", async function () { | ||
const res = await db | ||
.prepare("select * from users ORDER BY id DESC LIMIT 1;") | ||
.all(); | ||
|
||
// @ts-expect-error TODO | ||
deepStrictEqual(res.results[0].full_name, "Bobby Tables"); | ||
// @ts-expect-error TODO | ||
deepStrictEqual(res.results[0].favorite_color, "blue"); | ||
equal(res.success, true); | ||
equal(typeof res.meta.duration, "number"); | ||
}); | ||
|
||
test("can use the production studio to do inserts", async function () { | ||
const nextId = await getNextId(); | ||
await db | ||
.prepare( | ||
`INSERT INTO users (id, full_name, favorite_color) VALUES (${ | ||
nextId + 1 | ||
}, 'Bobby Tables', 'blue');` | ||
) | ||
.all(); | ||
|
||
const res = await db | ||
.prepare("select * from users ORDER BY id DESC LIMIT 1;") | ||
.all(); | ||
|
||
deepStrictEqual(res.results, [ | ||
{ | ||
full_name: "Bobby Tables", | ||
favorite_color: "blue", | ||
id: nextId + 1, | ||
}, | ||
]); | ||
equal(res.success, true); | ||
equal(typeof res.meta.duration, "number"); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
@tableland/studio-client
package is not published yet. Anyone wanting to try out this feature will have tonpm link
. Linking between to two monorepos is tricky. I can help with that via voice if needed.