SuperAPI: Extending any API member + Generates a Class wrapper + Intellisense Works

Want to add custom properties or functions to a instance in workspace, and interact with it the same way as normal? Look no Further

SuperAPI (Codename: Super hAPI)

I like making compilers, so I hacked up a way for anyone to extend any given class.
You can create multiple extensions for the same class from different modules, and really just build out your API how YOU think it should be.

A good example is I would personally extend DataStores to utilize ProfileService.

When you have a common API across all your games or projects, with the boiler-code already made by you previously, your production speed goes up.

This bring API composition closer to the developers, and really gives the ability to share APIs to multiple developers/customers, or across multiple experiences in a way that is Packaged, Portable, and separate from production-code.

EDIT: THIS IS A PROTOTYPE AND PRE-PRODUCTION

Pitch

Summary

You can use this to aid developers in developing plugin APIs, build APIs, tool APIs, and in-game APIs. Any number of things may be done with this! I will be using this to developer itself.

ALSO: Anything visible via the wiki will generate a Luau representation accurately. Anything not publicly disclosed by Roblox (Such as internal-to-roblox type definitions for QFont, QDir, Array, Dictionary, Variant, Objects to name a few. Default Type annotations were provided. “Broken” types [as far as Luau is concerned] like “CoordinateFrame?” and “float?” have been converted to “CFrame?” and “number?”)

If You have any ideas to commit, please post below! I’d like to build this out, and if it functions well, I’d like to release this plugin to the community!

The Plugin Would have a type introspection catalog, built-in insertable or removable pre-extensions, type generator, and ultimately a easy-search/edit menu as the main component.

The idea here is you can Package a self contained API (and attach metadata) and distribute it to anyone using this framework to enrich their development experience seamlessly… and on the base Coding level that’s compatible with Intellisense.

Extending a Type
Extensions are placed under the designated API Module, in a folder named Extensions.
Extensions must be immediate children of the Folder. Namespace collisions between publicly available extension modules are NOT resolved by this plugin, as that is more in the scope of user Interaction with their libraries, and developer interaction with their type identification.

All extensions are scanned via the plugin to find type definitions with the following format.
Example- Extending Instance

export type Extension_CLASSNAME = EXTENSION -- Example Format

export type Extension_Instance = { -- Example with Extending "Instance"
    PrintHelloWorld: ()->nil
}

Notice: A Module may contain multiple type Extensions in the same Script.

Initializing The Extension

The Module’s return must be named “module” within the source, and be a table.
local module = {} ... return module

The extension must have an initializer.
The Following format and example applies for Instance:

module.init_CLASSNAME = function(this: WRAPPED_CLASS) return PROXY end --Format

module.init_Instance = function(this: Instance)
    local proxy = require(...pathToProxyModule).proxy()
    --proxy.members, proxy.getters, proxy.setters, proxy.__metadata
    return proxy
end

Editing the proxy.members allows you to add properties and methods
Editing the proxy.getters allows you to assign a property to return the value from a function
Editing the proxy.setters allows you to assign a propert-set-action to go through a function-call.

Members
The following assumes we are implementing PrintHelloWorld from the above extension example.

module.init_Instance = function(this: Instance)
    local proxy = require(...pathToProxyModule).proxy()
    local proxy.this = this --Assign this to maintain wrapped object.
    local members = proxy.members
    function members.PrintHelloWorld()
        print('Hello World') -- You can now call this from the wrapped Object.
        --Intellisense will also show you the function suggestions!
    end
    return proxy
end

Getters/Setters, and __metadata

We are assuming we are just editing the proxy variable in this example:

module.init_Instance = function(this: Instance)
    local proxy = require(...pathToProxyModule).proxy()
    local proxy.this = this --Assign this to maintain wrapped object.
    local members = proxy.members
    local getters, setters = proxy.getters, proxy.setters
    proxy.__metaData = {GeneratedBy = "ExtensionDeveloperName"}
    
    proxy.getters.CheckIsPart = function() return this:IsA'BasePart' end
    --print(object.CheckIsPart) --will return the function calls return.
    proxy.setters.InGame = function(isInGame:boolean) 
        if isInGame == false then
                this:Destroy() --pointless, but just for example
        end
    end



    return proxy

How You can help

Right now this is a private plugin until I can get some community feedback about:

  • What should be included Extensions to the plugin for optional use? A UI Suite is a good start.
  • A Standard type library for optional inclusion (with full implementations for data only), examples including: Array, Dictionary, Stack, Map, Hash, any number of class structures for processing data.
  • What are some securities that are wanted before release of this plugin?

Thanks for reading

3 Likes

Wow! This looks really cool!

Time to do evil things…

1 Like

I’m working on a module called Accel that serves as a wrapper for Roblox services and core libs. This seems like it’d work pretty nicely alongside Accel, so I’m definitely interested.

sounds great and that you put a lot of effort into your project. However you should be a bit more humble about what you promise. Wrapping instances is something that has been tried quite a bit (you can search the forum) and it doesn’t seem to have transcended as one would expect. Personally there was a time when I was convinced it was a good idea, so I did something similar to what you are doing, but much less sophisticated. Then I learned a few things that I would like to share with you.

We can apply the concept of wrapping throughout the game. This involves wrapping virtually all instances, including those that are dynamically created. Even if the new API will not be used. In this case, depending on the number of instances, which are always usually quite a few, memory could be an issue. There is also extra access time that can become noticeable in certain contexts and debugging is definitely more difficult due to the same wrapper (this is one of the reasons why roblox-ts is not so popular).

So we would say that not everything should be wrapped. But then wrapping and unwrapping becomes typical (and exhausting) during development. While in the beginning it may be clear to decide what things should be wrapped and what should not, as the project gains features and adds changes it is not so easy to make that decision anymore.

An important question is: Exactly what problem does wrapping an instance solve? Specializing a class, increasing a level of abstraction, a common API, are pretty general things that point to software development in general. You would have to do the same things as always (move, copy, clone, animate instances) but under the new API standard. This is neater but not easier.

This comes from my personal experience using wrapper in Roblox game development and these are the main reasons that led me to abandon this idea considering them unsolvable in some cases. You have probably already solved a lot of this and I would like to know what your solutions were. However you should make it clear that not everything is advantages.

1 Like

First off: this was an extremely thoughtful reply.

I agree. I’m going to be adding several types of proxy creators that offer several speed and memory advantages/cons over the other.

As for wrapping and unwrapping? Well just as you said: not everything should be wrapped. Important services and objects Like Datamodel (game) and workspace and Lighting can be wrapped by several developers and imported by 1. You can have expedited imports of libraries just by the modules being detected in the game.

Mode of coding I think will work best:

Default mode for important services: Wrap and Extend,
Default mode for unimportant/repeatable objects: Wrap when needed, unwrap via “wrapped.__instance”
(Which is a hidden function within a wrapped object)

Beyond this; A lot of the problems this can solve is not yet ready for eyes :frowning:

1 Like