{"id":15821607,"url":"https://github.com/dgp1130/ctor-exp","last_synced_at":"2025-04-01T06:46:40.246Z","repository":{"id":115117102,"uuid":"307864037","full_name":"dgp1130/ctor-exp","owner":"dgp1130","description":null,"archived":false,"fork":false,"pushed_at":"2022-02-12T21:59:45.000Z","size":87,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-05T07:41:05.402Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dgp1130.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-28T00:34:40.000Z","updated_at":"2022-02-13T06:19:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"1169a84e-527f-4605-814a-404bbe713940","html_url":"https://github.com/dgp1130/ctor-exp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgp1130%2Fctor-exp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgp1130%2Fctor-exp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgp1130%2Fctor-exp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgp1130%2Fctor-exp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dgp1130","download_url":"https://codeload.github.com/dgp1130/ctor-exp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246598181,"owners_count":20802975,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-05T07:40:29.057Z","updated_at":"2025-04-01T06:46:35.239Z","avatar_url":"https://github.com/dgp1130.png","language":"TypeScript","readme":"# ctor-exp\n\nAn experimental implementation of `ctor\u003cT\u003e`, a new paradigm for constructing\nobjects as described in [Construct Better](https://blog.dwac.dev/posts/ctor/).\nThis is implemented as a simple TypeScript library because it's type system is\n~~abusable~~ powerful enough to provide most of the critical features of\n`ctor\u003cT\u003e`. Someone smarter than me could probably do better with a custom\ncompiler or plugin, but this is good enough for an experimental implementation.\n\nThis is not intended for production usage or anything beyond basic\nexperimentation. **DO NOT USE THIS IN REAL CODE**. You have been warned.\n\nRetrofitting an existing language's constructor semantics to fit a new paradigm\nis a losing battle and should not be done, I am merely doing it here for\nexplanatory purposes and to give developers something they can actually use in\norder to evaluate how effective this system actually is.\n\n## Installation\n\nInstall the package via NPM and import/require it accordingly. The only two\nsymbols exported are `ctor\u003cT\u003e`, and `from()` (described below).\n\n```shell\nnpm install ctor-exp\n```\n\n## Usage\n\nThis explains with very rough examples how to use `ctor\u003cT\u003e` as a constructor\nengine.\n\n### Basic Construction\n\nClasses should be defined with public constructors that follow the format\n(examples are TypeScript, JavaScript can be used by simply dropping the types):\n\n```typescript\nclass Foo {\n    // Declare class fields.\n    private foo: string;\n    public bar: number;\n    private readonly baz: boolean;\n    private other: string = 'something';\n\n    // Boilerplate, uninteresting constructor. In a real implementation this\n    // would be automatically generated by the compiler, but for this experiment\n    // it must be hand-written. See Invariants section for more info on the\n    // precise requirements of constructors.\n    public constructor({ foo, bar, baz }: {\n        foo: string,\n        bar: number,\n        baz: boolean,\n    }) {\n        // Directly assign constructor parameters to class fields, do not do any\n        // additional computation in the constructor.\n        this.foo = foo;\n        this.bar = bar;\n        this.baz = baz;\n        // Fields unrelated to the constructor (like `other`) can be left out.\n    }\n}\n```\n\nNow that the boilerplate constructor exists, we can use `ctor\u003cT\u003e` to construct\nit. We can introduce a factory to construct this type:\n\n```typescript\nimport { ctor } from 'ctor-exp';\n\nclass Foo {\n    // Snip - Class fields and constructor...\n\n    // Factory to use when creating a `Foo` object.\n    public static from(foo: string, bar: number): Foo {\n        const baz = foo.length === bar; // Do some work..\n\n        // Construct `Foo` when ready!\n        return ctor.new(Foo, { foo, bar, baz }).construct();\n    }\n}\n```\n\nUsing this system we have simple, boilerplate constructors and all the\ninitialization logic is performed in separate factories.\n\n### Inheritance\n\nWhat has been shown so far looks like a lot of boilerplate for simple\nconstructor patterns that could be easily followed in any existing programming\nlanguage. However, the tricky part is inheritance, so let's extend something!\n\n```typescript\nimport { ctor, from, Implementation } from 'ctor-exp';\n\nclass Foo {\n    public readonly foo: string;\n\n    // Boilerplate, uninteresting constructor.\n    public constructor({ foo }: { foo: string }) {\n        this.foo = foo;\n    }\n\n    // Factory for creating a `ctor\u003cFoo\u003e`, extendable by subclasses.\n    public static from(foo: string): ctor\u003cFoo\u003e {\n        const oof = foo.split('').reverse().join(''); // Do some work...\n\n        // Return the `ctor\u003cFoo\u003e`, just don't `.construct()` it yet.\n        return ctor.new(Foo, { foo: oof });\n    }\n}\n\n// Extend an implementation of `Foo`, generated by `ctor\u003cT\u003e`. This is necessary\n// to properly construct this extended class with `ctor\u003cT\u003e`.\nclass Bar extends Implementation\u003cFoo\u003e() {\n    public readonly bar: string;\n\n    // Also boilerplate, equally uninteresting constructor.\n    // Only difference is an empty `super()` call. No need to provide any\n    // parameters to `Foo`, `ctor\u003cT\u003e` will do that for you.\n    public constructor({ bar }: { bar: string }) {\n        super();\n        this.bar = bar;\n    }\n\n    // Factory for creating a `Bar`, composing `Foo.from()`.\n    public static from(foo: string, bar: string): Bar {\n        // Get a `ctor\u003cFoo\u003e` by calling `Foo`'s factory.\n        const fooCtor = Foo.from(foo);\n\n        // Constrct `Bar` by extending the return `ctor\u003cFoo\u003e`.\n        return from(fooCtor).new(Bar, { bar }).construct();\n    }\n}\n```\n\nUsing `from()`, we're able to cleanly compose and reuse the `ctor\u003cT\u003e` object\nreturned from `Foo.createFoo()`.\n\nYou should *never* extend a class directly. Always extend\n`Implementation\u003cMyParentClass\u003e()` instead.\n\nIf you ever want to call a superclass method, rather than using\n`super.method()`, you should use `this._super.method()`. This is just a quirk of\nhow the library is implemented on top of the existing JavaScript class paradigm.\n\n### Mixins\n\nBecause all classes extend `Implementation\u003cSomeSuperClass\u003e()`, it means that\nthey only have a type reference to their superclass, rather than a value\nreference. This is important because it means that all classes do not actually\nhave direct knowledge of their superclasses, only knowledge of the interface of\nthe superclass. This is particularly useful for\n[mixins](https://en.wikipedia.org/wiki/Mixin).\n\n```typescript\nimport { ctor, from, Implementation } from 'ctor-exp';\n\n// Declare a mixin which extends any object.\nclass Mixin extends Implementation() {\n    private readonly data: string;\n\n    // Another boilerplate, uninteresting constructor.\n    // Also need an empty `super()` call for mixins.\n    public constructor({ data }: { data: string }) {\n        super();\n        this.data = data;\n    }\n\n    public mixin(): string {\n        return this.data;\n    }\n\n    // Accept a parent `ctor` object of any type and extend it with `from()`.\n    // Can provide any constructor arguments to mixin and then return an\n    // intersection of the parent type and `Mixin`.\n    public static from\u003cMixinParent\u003e(parent: ctor\u003cMixinParent\u003e, data: string):\n            ctor\u003cMixin \u0026 MixinParent\u003e {\n        // Extend normally, except with the `.mixin()` function to allow any\n        // superclass type!\n        return from(parent).mixin(Mixin, { data });\n    }\n}\n\n// Parent class is unrelated to `Mixin` and has no knowledge of it.\nclass Parent {\n    public parent(): string {\n        return 'parent';\n    }\n\n    public static from(): ctor\u003cParent\u003e {\n        return ctor.new(Parent);\n    }\n}\n\n// Child extends `Parent` with `Mixin` included.\nclass Child extends Implementation\u003cMixin \u0026 Parent\u003e() {\n    public child(): string {\n        return this.parent() + this.mixin();\n    }\n\n    public static from(mixinData: string): Child {\n        // Get a `ctor\u003cParent\u003e` as normal.\n        const parentCtor = Parent.from();\n\n        // Extend with `Mixin`.\n        const mixinCtor = Mixin.from(parentCtor, mixinData);\n\n        // Further extend with `Child` and construct.\n        return from(mixinCtor).new(Child).construct();\n    }\n}\n```\n\nThis allows mixins to be defined without knowledge of a superclass and to be\nextended and constructed just like normal inheritance! You can also have type\nconstrained mixins, which enforce a particular interface on their superclass.\n\n```typescript\nimport { ctor, from, Implementation } from 'ctor-exp';\n\ninterface MixinParent {\n    parent(): string;\n}\n\n// Extend an unknown implementation of some interface.\nclass Mixin extends Implementation\u003cMixinParent\u003e() {\n    public mixin(): string {\n        return this.parent(); // Parent interface is usable.\n    }\n\n    // Define `parentCtor` as a `ctor\u003cMixinParent\u003e` and mixin normally.\n    public static from\u003cParent extends MixinParent\u003e(parentCtor: ctor\u003cParent\u003e):\n            ctor\u003cParent \u0026 Mixin\u003e {\n        return from(parentCtor).mixin(Mixin);\n    }\n}\n\n// A parent class implementing the required interface to support `Mixin`.\nclass GoodParent implements MixinParent {\n    public parent(): string {\n        return 'parent';\n    }\n\n    public static from(): ctor\u003cGoodParent\u003e {\n        return ctor.new(GoodParent);\n    }\n}\n\n// A child class which uses a valid superclass `GoodParent` with `Mixin`.\nclass GoodChild extends Implementation\u003cGoodParent \u0026 Mixin\u003e() {\n    public static from(): GoodChild {\n        const parentCtor = GoodParent.from();\n        // `parentCtor` satisfies `ctor\u003cMixinParent\u003e`\n        const mixinCtor = Mixin.from(parentCtor);\n        return from(mixinCtor).new(GoodChild);\n    }\n}\n\n// A parent class which does **not** implement the required interface to support\n// `Mixin`.\nclass BadParent {\n    public static from(): ctor\u003cBadParent\u003e {\n        return ctor.new(BadParent);\n    }\n}\n\n// A child class which uses an invalid superclass `BadParent` with `Mixin`.\nclass BadChild extends Implementation\u003cBadParent \u0026 Mixin\u003e() {\n    public static from(): BadChild {\n        const parentCtor = BadParent.from();\n        // COMPILE ERROR: `parentCtor` does not satisfy `ctor\u003cMixinParent\u003e`.\n        const mixinCtor = Mixin.from(parentCtor);\n        return from(mixinCtor).new(BadChild);\n    }\n}\n```\n\nThese mixins look and act just like traditional classes. There is no need for\na function which transforms a class definition to include mixin funcitonality\nas is normally necessary in JavaScript.\n\n## Examples\n\nThe idea of using a \"dumb\" constructor that\nonly assigns class fields combined with composeable factories that perform the\nreal business logic in a form which cleanly supports inheritance allows an\neasier implementation of many common problems in computer science.\n\nCheck out some examples which take simple use cases and show how they can be\nsurprisingly tricky using traditional constructors. Then look at the `ctor\u003cT\u003e`\nimplementation to see how much simpler these solutions can be.\n\n*   [Basic use](./src/ctor_test.ts)\n*   [Factory composition](./src/factories_test.ts)\n*   [Dependency injection of `ctor\u003cT\u003e`](./src/injection_test.ts)\n*   [Deep cloning objects](./src/clone_test.ts)\n*   [Serialization/deserialization](./src/serialization_test.ts)\n*   [Constructor coupling](./src/coupling_test.ts)\n*   [Mixins](./src/mixin_test.ts)\n*   [An optimized set using multiple superclass implementations](./src/optimized_set_test.ts)\n*   [Framework](./src/framework_test.ts)\n\n## Invariants\n\nSince this constructor paradigm is intended for a brand new programming\nlanguage, not all of its features/restrictions can be implemented in this\nexperimental library. As a result, there are a few invariants to keep in mind\nwhen using it to ensure that you are using it in a way that would be supported\nby a real compiler. Some of these restrictions are partially enforced by the\ntype systems, others are not.\n\n*   Constructors **must** merely assign parameters to class fields and should\n    not contain any additional logic. Such logic should be implemented in\n    factories.\n*   Constructors *should* use named parameters, but that is not strictly\n    necessary in this implementation.\n*   Subclasses **must** extend `Implementation\u003cSuperClass\u003e` and should **never**\n    extend a `SuperClass` directly.\n*   When calling a superclass method, you **must** use `this._super.method()`\n    and never use `super.method()`, as it won't have the method you are calling.\n*   Mixins with type constraints on their superclass **must** use factories\n    which accept a parent `ctor\u003cT\u003e` which explicitly `extends` the superclass\n    type.\n    *   The library may incorrectly allow superclass implementations which do\n        not satisfy the superclass type if you forget to do this.\n*   Do not call `new Foo()` directly on a subclass. It is reasonable to use\n    `new` on a class which does not extend another parent class, however\n    subclasses must always be constructed with\n    `from(parentCtor).new(/* ... */)`.\n*   `ctor.new(Foo, /* ... */)` should only be used within `Foo` itself (via\n    methods on the class).\n    *   Invoking `ctor.new()` is an implementation detail of the class being\n        constructed.\n    *   Subclasses should **not** call `ctor.new(ParentClass, /* ... */)`, they\n        should call a factory which returns `ctor\u003cParentClass\u003e`.\n    *   Calling `.construct()` is perfectly reasonable from any context.\n*   `from(ctor\u003cSuperClass\u003e)` should only be used to immediately call\n    `.new(SubClass, /* ... */)` on its result.\n    *   The type returned by `from()` is an implementation detail of `ctor\u003cT\u003e`,\n        which should not be observed by the program.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgp1130%2Fctor-exp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdgp1130%2Fctor-exp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgp1130%2Fctor-exp/lists"}