-
Notifications
You must be signed in to change notification settings - Fork 26
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(js-client)!: Segregation of responsibility between js-client packages [fixes DXJ-525] #378
Conversation
DXJ-525 Ensure validation for js-client services
Need to add validation for:
|
FluencePeer as FluencePeer$$ | ||
} from '@fluencelabs/js-client'; | ||
|
||
${converters}`; |
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.
I don't think this is a good idea to hardcode the body of these functions in each compiled file
Don't understand the reason to do this
You already export v5_callFunction
, v5_registerService
and FluencePeer
from '@fluencelabs/js-client'
why not export these functions as well from '@fluencelabs/js-client'
and use them normally
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.
It went through hard refactoring process and end up in the aqua-to-js
as i decided to completely remove any schema mentions from js-client. It uses schema to perform conversion. We have 3 options
- Make an exception for this case (easier)
- Minify and uglify this snippet to minimize it's impact on frontend (every function from every file would be bundled here)
- Support batch compilation in
aqua-to-js
package. As a result every batch compilation will generate a singleconverter.js
file
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.
Decided to move it back to js-client
packages/@tests/aqua/src/index.ts
Outdated
|
||
console.log("marine test finished, result: ", marine); | ||
// TODO: Currently this behavior will timeout after TTL bcs this function don't call 'responseSrc'. Will be attended in the next updates |
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.
are you sure this matter can be safely moved to another issue? Is safe to release js-client without this fixed? If not - I would rather solve this issue before merging
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.
Actually it wasn't working before. For aqua function without return values (no responseSrv calls) it's impossible to determine when function call should finish. After your comment I reviewed that, fixed this particular test with hacks. But it still will break on some cases in parallel air. To reliably fix this we need to wait for LNG-286
return null; | ||
} else if (schema.tag === "option") { | ||
if (!Array.isArray(value)) { | ||
throw new Error("Bad schema"); |
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.
I think a better error message could be useful, if you think a bit about when this error pottentially can happen. I would create an error text that explains what went wrong in general (e.g. Wasn't able to convert aqua to js
) and then for each case some particulars that could be useful for debugging (e.g.schema generated by the aqua comiler expects an option and options are represented as js arrays but found type: ${typeof value}: ${value}
) If aqua2ts
recursive function also accepts the third argument which is context
- then you can also store some context that can be useful to display in the error so it says how deep the error accured e.g. error happened at: someProperty.someNestedProperty.someNetedArray[0]
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.
These errors are impossible actually. Only if you write your own wrong schema by hand and use it. But this is not the case. I think that no need to spend much efforts improving this. This made for satisfying ts
, not for giving decent error messages.
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.
These errors are tottally possible if something goes wrong with the compiler and for guys who write the compiler at least a little effort here would be useful to understand where the problem happened
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.
Well, probably we can do some runtime validation here
]; | ||
const registerServiceArgs = | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
(srvDef.defaultServiceId as DefaultServiceId).s_Some__f_value != null |
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.
this looks pretty hacky to be honest. Sometimes defaultServiceId is not a string and has some weird property in it? I would avoid this, should be possible somehow
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.
I dunno how to avoid :)
It's actual value that comes from aqua compiler
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.
We can discuss this someday with aqua team. I created a task
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.
if this comes from the compiler why not put this in types instead of just putting string
there and asserting the type?
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.
Because these types are generated from compiler team :)
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.
It's 1 more question to ask how to split these types. I'm feeling ok to keep them in aqia-api
.
const argNames = [${argNames | ||
.map((arg) => { | ||
return `"${arg}"`; | ||
}) | ||
.join(", ")}]; | ||
const argCount = argNames.length; | ||
let peer = undefined; | ||
if (args[0] instanceof FluencePeer$$) { | ||
peer = args[0]; | ||
args = args.slice(1); | ||
} | ||
|
||
|
||
const callArgs = Object.fromEntries(args.slice(0, argCount).map((arg, i) => [argNames[i], arg])); | ||
|
||
const params = ({ | ||
peer, | ||
args: callArgs, | ||
config: args[argCount] | ||
}); | ||
|
||
const result = await callFunction$$({ | ||
script: ${scriptConstName}, | ||
...params, | ||
}); |
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.
this part also feels strange to hard-code and duplicate for each compiled function
I believe there is no reason for compiled wrappers to contain any duplicated code
They should literally contatin only the functions to call with hardcoded air and schemas passed down to them
If you want to move this internally from one package to another for some reason - this is fine
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.
if we are able to do ts inference from schemas some day then it would be probably enough to just compile aqua and save the result as JSON
{
"services": {
"service1": {
"schema": ...
},
...
},
"functions": {
"function1": {
"schema": ...
"air": ...
}
}
}
and then on ts side
import someAqua from "compiled-aqua.json"
import { aquaToJs } from "@fluencelabs/js-client"
// service1 and function1 types are infered from compiled-aqua.json
const { registerService1, function1 } = aquaToJs(someAqua)
so nothing has to be duplicated at all
or e.g additionally generate simple ts files that would just do something like this:
import { aquaToJs } from '@fluencelabs/js-client'
const someCompiledAqua = { ... }
export default aquaToJs(someCompiledAqua)
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.
Removed all dublicated code
if (issue.code === z.ZodIssueCode.invalid_type) { | ||
if (issue.path.length === 1 && typeof issue.path[0] === "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.
can be combined into one if
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.
for some reason no, ts won't infer the type correctly
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.
Yes, this case can be fixed as you suggested. I thought that it was about very similar case in conversion.ts
file
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
} argument(s). Got ${ctx.data.length}`, |
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.
I suggest to have a variable where you access this any and disable esling only once
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.
Not possible to do this somewhere above these conditions. This prop available only when condition is met
req: CallServiceData, | ||
): [z.infer<T>, null] | [null, string] => { | ||
const result = schema.safeParse(req.args, { | ||
errorMap: (issue, ctx) => { |
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.
I would consider having another function that just returns a string and for this function to only do
return { message: getErrorMsg(issue, ctx) }
so you don't repeat { message:
part everywhere. Up to you
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.
Nice suggestion. I don't want to refactor this rn because not sure about error messages completely. Probably i will do message reporting in other way, e.g. add a dedicated lib.
concat: withSchema(z.array(z.array(z.unknown())))((args) => { | ||
// concat accepts only 'never' type | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const arr = args as never[][]; | ||
return success([].concat(...arr)); | ||
}), |
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.
can we do?
concat: withSchema(z.array(z.array(z.unknown())))((args) => { | |
// concat accepts only 'never' type | |
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | |
const arr = args as never[][]; | |
return success([].concat(...arr)); | |
}), | |
concat: withSchema(z.array(z.array(jsonSchema)))((args) => { | |
return success(args.flat()); | |
}) |
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.
It will add slow runtime check which is completely useless in this scenario
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.
what about .flat()
with typecast and explanation?
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.
Nice!
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const res = "".concat(...(req.args as string[])); | ||
concat_strings: withSchema(z.array(z.string()))((args) => { | ||
const res = "".concat(...args); | ||
return success(res); |
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.
maybe just
return success(res); | |
return success(args.join('')); |
throw new SchemaValidationError(path, schema, "string", arg); | ||
} | ||
} else { | ||
throw new SchemaValidationError(path, schema, "never", arg); |
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.
maybe would be more useful
throw new SchemaValidationError(path, schema, "never", arg); | |
throw new SchemaValidationError(path, schema, schema.name, arg); |
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.
or maybe even more useful would be to throw a more actioonable error that would explain that this ${schema.name}
is unsupported please update js-client
CLI PR with passing tests |
@@ -95,7 +95,7 @@ async function checkConsistency(file, versionsMap) { | |||
|
|||
for (const [name, versionInDep] of versionsMap) { | |||
const check = (x, version) => { | |||
if (version.includes("*")) { | |||
if (version.includes("*") || version.includes("^")) { |
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.
What does this mean? What does this do?
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.
This file bumps version in ci to support e2e testing. Here i added support for workspace symbol.
@@ -20,10 +20,12 @@ | |||
"base64-js": "1.5.1" | |||
}, | |||
"devDependencies": { | |||
"@fluencelabs/aqua-api": "0.12.4-main-cee4448-2196-1", |
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.
maybe use normal version here?
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.
I will replace them later after releases in aqua and registry. Just don't want to block on them
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.
added a todos on these
"@fluencelabs/trust-graph": "3.1.2" | ||
"@fluencelabs/aqua-to-js": "workspace:*", | ||
"@fluencelabs/js-client": "workspace:*", | ||
"@fluencelabs/registry": "0.8.8-1", |
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.
this version also looks interesting. I wonder why
@@ -18,12 +18,14 @@ | |||
"ts-pattern": "5.0.5" | |||
}, | |||
"devDependencies": { | |||
"@fluencelabs/aqua-api": "0.12.0", | |||
"@fluencelabs/aqua-api": "0.12.4-main-cee4448-2196-1", |
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.
and here as well
@@ -52,8 +53,7 @@ export default async function aquaToJs<T extends OutputType>( | |||
sources: generateSources(res, "js", packageJson), | |||
types: generateTypes(res, packageJson), | |||
} | |||
: // TODO: probably there is a way to remove this type assert | |||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | |||
: // eslint-disable-next-line @typescript-eslint/consistent-type-assertions |
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.
You can avoid casting here if you just use overloads here as I suggested long ago when this function first appeared
type JsOutput = {
sources: string;
types: string;
};
type TsOutput = {
sources: string;
};
type NothingToGenerate = null;
export default async function aquaToJs(
res: CompilationResult,
outputType: "js",
): Promise<JsOutput | NothingToGenerate>;
export default async function aquaToJs(
res: CompilationResult,
outputType: "ts",
): Promise<TsOutput | NothingToGenerate>;
export default async function aquaToJs(
res: CompilationResult,
outputType: "js" | "ts",
): Promise<JsOutput | TsOutput | NothingToGenerate> {
if (
Object.keys(res.services).length === 0 &&
Object.keys(res.functions).length === 0
) {
return null;
}
const packageJson = await getPackageJsonContent();
if (outputType === "js") {
return {
sources: generateSources(res, "js", packageJson),
types: generateTypes(res, packageJson),
};
}
return {
sources: generateSources(res, "ts", packageJson),
};
}
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.
nice
/** | ||
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly. | ||
* The options allows to specify the timeout for that message in milliseconds. | ||
* If not specified the default timeout will be used | ||
*/ |
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.
interesting to know if these doc comments still appear in IDE in autocomplete as they previously did when zod was not used. If they are lost - I would think about somehow restoring them
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.
Yes, comments should be preserved. I checked
export class ExpirationError extends Error {} | ||
|
||
export class InterpreterError extends Error {} | ||
|
||
export class SendError extends Error {} |
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.
Could you please explain what do these solve exactly?
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.
I will add comments.
I separated errors to improve assertion with instanceof
operator
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.
are these errors exposed to the user so he can use instanceof
? Or you only use instanceof
yourself somewhere?
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.
They used in tests right now. Helps to validate specific error. It's just replacement of the old bad API with 10 different statuses. I will add more errors to cover every case and open them for user. Right now if user wants to check error type, he can use error message
field
${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}} | ||
${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]} | ||
${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3} | ||
${"op"} | ${"array_length"} | ${[]} | ${1} | ${"array_length accepts exactly one argument, found: 0"} | ||
${"op"} | ${"array_length"} | ${[]} | ${1} | ${"Expected 1 argument(s). Got 0"} |
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.
If these errors happen, can you as a developer understand that error happened in e.g. array_length function? Is there a stack trace? If for developers it's possbile to understand this then probably these errors are ok. If not - then previous erros where deffinetly better
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.
I added wrappers for printing service and function errors
error: []; | ||
signature: [number[]]; | ||
success: true; | ||
} | ||
| { | ||
error: string; | ||
signature: null; | ||
error: [string]; | ||
signature: []; |
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.
I am just curious why this change was required
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.
After we removed schema from js-client internals, there is no aqua->js casting right before service calls. So now internal services should work with aqua types directly.
* Review fixes * remove logs * Fixes * Todo to remove prefix later * Refactor service signatures * Fixes * Update lock file * Fix lockfile * Update deps * More fixes and renames * Fix compiler * Peer refactoring and cutting onConnectionChange API * Revert deleted API
What have been done