"
46 | ],
47 | "prettier": {
48 | "semi": false,
49 | "singleQuote": false,
50 | "arrowParens": "always",
51 | "trailingComma": "all"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/examples/cra/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/favicon.ico
--------------------------------------------------------------------------------
/examples/cra/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/cra/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/logo192.png
--------------------------------------------------------------------------------
/examples/cra/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/logo512.png
--------------------------------------------------------------------------------
/examples/cra/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/cra/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/cra/src/App.css:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | box-sizing: border-box;
5 | }
6 |
7 | .App {
8 | text-align: center;
9 | }
10 | .App code {
11 | background: #fff3;
12 | padding: 4px 8px;
13 | border-radius: 4px;
14 | }
15 | .App p {
16 | margin: 0.4rem;
17 | }
18 |
19 | .App-logo {
20 | height: 40vmin;
21 | pointer-events: none;
22 | }
23 |
24 | @media (prefers-reduced-motion: no-preference) {
25 | .App-logo {
26 | animation: App-logo-spin infinite 20s linear;
27 | }
28 | }
29 |
30 | .App-header {
31 | background-color: #282c34;
32 | min-height: 100vh;
33 | display: flex;
34 | flex-direction: column;
35 | align-items: center;
36 | justify-content: center;
37 | font-size: calc(10px + 2vmin);
38 | color: white;
39 | }
40 |
41 | .App-link {
42 | color: #61dafb;
43 | }
44 |
45 | @keyframes App-logo-spin {
46 | from {
47 | transform: rotate(0deg);
48 | }
49 | to {
50 | transform: rotate(360deg);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/cra/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react"
2 | import { configure } from "mobx"
3 | import { MyRootCont } from "./containers/_container.hooks"
4 | import { getMainPizzaAppContainer } from "./containers/_root.store"
5 | import { Main } from "./Main"
6 | import "./App.css"
7 |
8 | // don't allow state modifications outside actions
9 | configure({ enforceActions: "always" })
10 |
11 | interface AppProps {}
12 |
13 | function App({}: AppProps) {
14 | const store = useMemo(() => getMainPizzaAppContainer(), [])
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export default App
25 |
--------------------------------------------------------------------------------
/examples/cra/src/DomReady.todo-example.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 |
3 | // async flow
4 | class domController {
5 | constructor() {
6 | this._domReady = new Promise((resolve, reject) => {
7 | this.cb = resolve
8 | })
9 | }
10 |
11 | markDomAsDone() {
12 | this.cb()
13 | }
14 |
15 | domIsReady() {
16 | return this._domReady
17 | }
18 | }
19 |
20 | export async function provideProductDriverContainer(domController, a) {
21 | await domController.domIsReady()
22 |
23 | const pd = new PD()
24 | await pd.initRenderer()
25 |
26 | await wait(200)
27 | const b2 = new B2(auth.auth, a.a1)
28 |
29 | return {
30 | b1,
31 | b2,
32 | }
33 | }
34 |
35 | export const Main = () => {
36 | const { domController } = useStufContainer()
37 | useEffect(() => {
38 | setTimeout(() => {
39 | domController.markDomAsDone()
40 | }, 500)
41 | }, [])
42 | return (
43 |
44 | Main Component:
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/examples/cra/src/Main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useContainer } from "./containers/_container.hooks"
3 | import { MainLayoutControl } from "./components/MainLayoutControl"
4 |
5 | export const Main = () => {
6 | return (
7 |
10 | )
11 | }
12 |
13 | export const Profile = () => {
14 | const [a] = useContainer().aCont
15 | if (!a) return null
16 | const { a1, a2 } = a
17 |
18 | return (
19 | <>
20 | {a1.getName()}
21 |
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/examples/cra/src/components/Controls.module.css:
--------------------------------------------------------------------------------
1 | .controlsSections {
2 | display: flex;
3 | flex-direction: column;
4 | height: 100%;
5 | justify-content: space-around;
6 | }
7 |
8 | .controlsSections > * + * {
9 | border-top: 1px solid lightgray;
10 | padding-top: 16px;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/cra/src/components/EnsureKitchen.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react"
2 | import { generateEnsureContainerSet } from "iti-react"
3 | import { useContainerSet } from "../containers/_container.hooks"
4 |
5 | const x = generateEnsureContainerSet(() =>
6 | useContainerSet(["kitchen", "pizzaContainer", "auth"]),
7 | )
8 | export const EnsureKitchenProvider = x.EnsureWrapper
9 | export const useNewKitchenContext = x.contextHook
10 |
--------------------------------------------------------------------------------
/examples/cra/src/components/ErrControls.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react"
2 | import { observer } from "mobx-react-lite"
3 | import { useContainer, useContainerSet } from "../containers/_container.hooks"
4 | import { EnsureKitchenProvider, useNewKitchenContext } from "./EnsureKitchen"
5 |
6 | export const ErrControls = () => {
7 | return (
8 |
9 | new Err Controls:
10 | {/*
11 |
12 | */}
13 |
14 |
15 | )
16 | }
17 |
18 | const Simple = () => {
19 | const [a, err] = useContainer().aCont
20 | if (!a) return <>A is loading>
21 | return Sync Err: {a.a1.getName()}
22 | }
23 |
24 | const SimpleSyncErr = () => {
25 | const [a, err] = useContainer().errSync
26 | if (!err) {
27 | return null
28 | }
29 |
30 | return Sync Err1: {err.message}
31 | }
32 |
33 | const SimpleAsyncErr = () => {
34 | const [a, err] = useContainer().errAsync
35 | if (!err) {
36 | return null
37 | }
38 |
39 | return Async Sync Err2: {err.message}
40 | }
41 |
42 | const NestedErr = () => {
43 | const [a, err] = useContainer().errNested
44 | console.log("~~~ err", err)
45 | if (!a) {
46 | return null
47 | }
48 |
49 | console.log(a)
50 |
51 | return Async Nested Sync Err2:
52 | }
53 |
--------------------------------------------------------------------------------
/examples/cra/src/components/MainLayoutControl.module.css:
--------------------------------------------------------------------------------
1 | .controls {
2 | display: grid;
3 | grid-template-columns: 3fr 1fr;
4 | grid-template-rows: 3fr 1fr;
5 | grid-column-gap: 0px;
6 | grid-row-gap: 0px;
7 | }
8 |
9 | .main {
10 | grid-area: 1 / 1 / 2 / 2;
11 | }
12 | .aside {
13 | grid-area: 1 / 2 / 2 / 3;
14 | }
15 | .bottom {
16 | grid-area: 2 / 1 / 3 / 3;
17 | }
18 |
--------------------------------------------------------------------------------
/examples/cra/src/components/MainLayoutControl.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import s from "./MainLayoutControl.module.css"
3 | import { PizzaPlace } from "./PizzaPlace"
4 | import { Controls } from "./Controls"
5 |
6 | export const MainLayoutControl = () => {
7 | return (
8 | <>
9 |
10 |
13 |
14 |
15 |
16 |
17 |
Pizza Place1
18 |
19 |
20 | >
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/cra/src/components/PizzaPlace.module.css:
--------------------------------------------------------------------------------
1 | .pizzaPlace {
2 | display: grid;
3 | }
4 |
5 | .kitchenData {
6 | display: flex;
7 | flex-direction: row;
8 | width: 100%;
9 | justify-content: space-around;
10 | }
11 |
12 | /* .bricks {
13 | background-color: silver;
14 | background-image: linear-gradient(335deg, #b00 23px, transparent 23px),
15 | linear-gradient(155deg, #d00 23px, transparent 23px),
16 | linear-gradient(335deg, #b00 23px, transparent 23px),
17 | linear-gradient(155deg, #d00 23px, transparent 23px);
18 | background-size: 58px 58px;
19 | background-position: 0px 2px, 4px 35px, 29px 31px, 34px 6px;
20 | } */
21 |
--------------------------------------------------------------------------------
/examples/cra/src/components/PizzaPlace.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { observer } from "mobx-react-lite"
3 | import cx from "classnames"
4 | import s from "./PizzaPlace.module.css"
5 | import { useContainer } from "../containers/_container.hooks"
6 |
7 | export const PizzaPlace = observer(() => {
8 | const [pizza] = useContainer().pizzaContainer
9 | if (!pizza) return <>Pizza Place is loading>
10 |
11 | const { pizzaPlace, diningTables } = pizza
12 |
13 | return (
14 | <>
15 |
16 |
Pizza Place: {pizzaPlace.name}
17 | Open?: {pizzaPlace.isOpen ? "true" : "false"}
18 | Dining Tables: {diningTables.tables.length}
19 |
20 |
21 |
22 | >
23 | )
24 | })
25 |
26 | export const KitchenData = observer(() => {
27 | const [kitchenCont] = useContainer().kitchen
28 |
29 | if (!kitchenCont) return <>Kitchen is loading>
30 |
31 | const { kitchen, orderManager } = kitchenCont
32 | return (
33 | <>
34 | Kitchen data: ({kitchen.kitchenName})
35 |
36 |
37 |
38 |
Orders:
39 |
40 | {orderManager.orders.map((order, idx) => {
41 | return (
42 | -
43 | table: {order.table.name} | pizzastate: {order.pizza.state}
44 |
45 | {order.pizza.ingredients.map((ingredient, idx) => (
46 | - {ingredient.name}
47 | ))}
48 |
49 |
50 | )
51 | })}
52 |
53 |
54 |
55 |
Ingredients:
56 |
57 |
58 |
59 | >
60 | )
61 | })
62 |
63 | export const Inventory = observer(() => {
64 | const [kitchenCont] = useContainer().kitchen
65 | if (!kitchenCont) return <>Kitchen is loading>
66 | const { ingredients } = kitchenCont
67 | return (
68 | <>
69 |
70 | {ingredients.ingredientsStats.map(([name, count], idx) => (
71 | -
72 | test {name} - {count as string}
73 |
74 | ))}
75 |
76 | >
77 | )
78 | })
79 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/_AppRoot.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useMemo } from "react"
2 | import { MyRootCont } from "../containers/_container.hooks"
3 | import { getMainPizzaAppContainer } from "../containers/_root.store"
4 |
5 | export function PizzaAppWrapper({ children }: { children: ReactNode }) {
6 | const store = useMemo(() => getMainPizzaAppContainer(), [])
7 |
8 | return {children}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/_container.hooks.ts:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react"
2 | import { getContainerSetHooks } from "iti-react"
3 | import { PizzaAppContainer } from "./_root.store"
4 |
5 | export const MyRootCont = React.createContext({})
6 |
7 | let mega = getContainerSetHooks(MyRootCont)
8 | export const useContainerSet = mega.useContainerSet
9 | export const useContainer = mega.useContainer
10 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/_root.store.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 |
3 | import { provideAContainer } from "./container.a"
4 | import { provideAuthContainer } from "./container.auth"
5 | import { provideBContainer } from "./container.b"
6 | import { provideKitchenManipulatorContainer } from "./container.kitchein-manipulator"
7 | import { providePizzaPlaceContainer } from "./container.pizza-place"
8 | import { provideKitchenContainer } from "./container.kitchen"
9 |
10 | import { provideFatLib1 } from "./container.fat-lib1"
11 | import { provideFatLib2 } from "./container.fat-lib2"
12 |
13 | export type PizzaAppCoreContainer = ReturnType
14 | export function pizzaAppCore() {
15 | return createContainer()
16 | .add(() => ({
17 | auth: async () => provideAuthContainer(),
18 | }))
19 | .add((c) => ({
20 | aCont: async () => provideAContainer(await c.auth),
21 | }))
22 | .add((ctx) => ({
23 | bCont: async () => provideBContainer(await ctx.auth, await ctx.aCont),
24 | }))
25 | .add((c) => ({
26 | // fat libs
27 | fatlib1: async () => provideFatLib1(),
28 | fatlib2: async () => provideFatLib2(),
29 | }))
30 | .add((ctx, node) => ({
31 | // pizza stuff
32 | pizzaContainer: async () => providePizzaPlaceContainer(await ctx.fatlib2),
33 | kitchen: async () => provideKitchenContainer(),
34 | }))
35 | .add((ctx) => ({
36 | errSync: () => {
37 | throw new Error("errSync")
38 | },
39 | errAsync: async () => {
40 | throw new Error("errAsync")
41 | },
42 | errNested: async () => ({
43 | nestedSyncErr: () => {
44 | throw new Error("errSync")
45 | },
46 | nestedAsyncErr: async () => {
47 | throw new Error("nestedAsyncErr")
48 | },
49 | }),
50 | }))
51 | }
52 |
53 | export type PizzaAppContainer = ReturnType
54 | export function getMainPizzaAppContainer() {
55 | return pizzaAppCore().add((ctx, node) => ({
56 | kitchenManipulator: async () => provideKitchenManipulatorContainer(node),
57 | }))
58 | }
59 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/_utils.ts:
--------------------------------------------------------------------------------
1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w))
2 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.a.ts:
--------------------------------------------------------------------------------
1 | import type { AuthContainer } from "./container.auth"
2 | import { A1, A2, A3 } from "../stores/store.a"
3 |
4 | export interface A_Container {
5 | a1: A1
6 | a2: A2
7 | a3: A3
8 | }
9 |
10 | export async function provideAContainer(
11 | auth: AuthContainer,
12 | ): Promise {
13 | const a1 = new A1(auth.auth)
14 | const a2 = new A2(a1, auth.auth)
15 | const a3 = new A3(a1, a2)
16 |
17 | return {
18 | a1,
19 | a2,
20 | a3,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.auth.ts:
--------------------------------------------------------------------------------
1 | import { Authorization } from "../stores/store.authorization"
2 | import { Auth } from "../stores/store.auth"
3 | import { wait } from "./_utils"
4 |
5 | export interface AuthContainer {
6 | auth: Auth
7 | authorization: Authorization
8 | }
9 |
10 | export async function provideAuthContainer(): Promise {
11 | const auth = new Auth()
12 | await wait(50)
13 | await auth.getToken()
14 | let x = await auth.getUserType()
15 | return {
16 | auth: auth,
17 | authorization: new Authorization(x),
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.b.ts:
--------------------------------------------------------------------------------
1 | import type { A_Container } from "./container.a"
2 | import type { AuthContainer } from "./container.auth"
3 | import { B1, B2 } from "../stores/store.b"
4 | import { wait } from "./_utils"
5 |
6 | export interface B_Container {
7 | b1: B1
8 | b2: B2
9 | }
10 |
11 | export async function provideBContainer(
12 | auth: AuthContainer,
13 | a: A_Container,
14 | ): Promise {
15 | const b1 = new B1(auth.auth, a.a2)
16 |
17 | await wait(70)
18 | const b2 = new B2(auth.auth, a.a1)
19 |
20 | return {
21 | b1,
22 | b2,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.config.ts:
--------------------------------------------------------------------------------
1 | import { DayType, getDayType } from "../services/url-param"
2 | import { wait } from "./_utils"
3 |
4 | export interface B_Container {
5 | dayType: DayType
6 | }
7 |
8 | export async function provideConfigContainer(): Promise {
9 | const b1 = getDayType()
10 | await wait(90)
11 |
12 | return {
13 | dayType: b1,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.fat-lib1.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fat Lib 1 shows an example how we can use async modules from react
3 | */
4 | export async function provideFatLib1() {
5 | console.log("~~~ provider for fat lib initiated 0")
6 | let x = await import("../fat-libs/fat-lib1")
7 | console.log("~~~ provider for fat lib initiated 1")
8 | console.log(x)
9 | let s = x.fatLib300kb()
10 |
11 | return {
12 | pizzaPlace: 12,
13 | fatLibData: s,
14 |
15 | getFatLibData: async function () {
16 | let x = await import("../fat-libs/fat-lib1")
17 | console.log("~~~ provider for fat lib initiated 1")
18 | console.log(x)
19 | x.fatLib300kb()
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.fat-lib2.ts:
--------------------------------------------------------------------------------
1 | import type { FatLib2_3MB } from "../fat-libs/fat-lib2"
2 | import type { GetContainerFormat } from "iti"
3 |
4 | let x: FatLib2_3MB
5 | /**
6 | * Fat Lib 1 shows an example how we can use async modules from react
7 | */
8 | export async function provideFatLib2() {
9 | console.log("~~~ provider for fat lib 2 initiated 0")
10 | return {
11 | getFatLib2Data: async function () {
12 | let { FatLib2_3MB } = await import("../fat-libs/fat-lib2")
13 |
14 | console.log("~~~ provider for fat lib 2 initiated 1")
15 | console.log(FatLib2_3MB)
16 |
17 | return new FatLib2_3MB()
18 | },
19 | }
20 | }
21 |
22 | // export interface FatLib2Container {
23 | // getFatLib2Data: () => Promise
24 | // }
25 |
26 | export type FatLib2Container = GetContainerFormat
27 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.kitchein-manipulator.ts:
--------------------------------------------------------------------------------
1 | import { KitchenSizeUIController } from "../stores/_controllers/controller.kitchen"
2 | import { provideUpgradedKitchenContainer } from "./container.kitchen"
3 | import type { PizzaAppCoreContainer } from "./_root.store"
4 |
5 | export interface KitchenManipulator_Container {
6 | kitchenSizeController: KitchenSizeUIController
7 | }
8 |
9 | export interface KitchenUpgrader {
10 | upgradeKitchenConatiner: () => Promise
11 | }
12 |
13 | export async function provideKitchenManipulatorContainer(
14 | node: PizzaAppCoreContainer,
15 | ): Promise {
16 | let ksc = new KitchenSizeUIController({
17 | onKitchenResize: async () => {
18 | const currentKitchen = await node.items.kitchen
19 | return await node.upsert({
20 | kitchen: () => provideUpgradedKitchenContainer(currentKitchen),
21 | })
22 | },
23 | })
24 |
25 | return {
26 | kitchenSizeController: ksc,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.kitchen.ts:
--------------------------------------------------------------------------------
1 | import { IngredientsService } from "../services/ingredients-manager"
2 | import type { Ingredients } from "../stores/store.ingrediets"
3 | import { Kitchen, OrderManager } from "../stores/store.kitchen"
4 | import { Oven } from "../stores/store.oven"
5 |
6 | export interface Kitchen_Container {
7 | oven: Oven
8 | ingredients: Ingredients
9 | orderManager: OrderManager
10 | kitchen: Kitchen
11 | }
12 |
13 | export interface KitchenUpgrader {
14 | upgradeKitchenConatiner: () => Promise
15 | }
16 |
17 | export async function provideKitchenContainer(): Promise {
18 | let oven = new Oven()
19 | let ingredients = await IngredientsService.buySomeIngredients()
20 |
21 | let kitchen = new Kitchen(oven, ingredients)
22 | let orders = new OrderManager(kitchen)
23 |
24 | return {
25 | oven: oven,
26 | orderManager: orders,
27 | ingredients: ingredients,
28 | kitchen: kitchen,
29 | }
30 | }
31 |
32 | export async function provideUpgradedKitchenContainer(
33 | prevContainer: Kitchen_Container,
34 | ): Promise {
35 | let biggerOven = new Oven(8)
36 |
37 | // This is one way of migrating data
38 | let kitchen = new Kitchen(biggerOven, prevContainer.ingredients)
39 | let orderManager = new OrderManager(kitchen)
40 | orderManager.orders = prevContainer.orderManager.orders
41 |
42 | return {
43 | oven: biggerOven,
44 | orderManager: orderManager,
45 | ingredients: prevContainer.ingredients,
46 | kitchen: kitchen,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/container.pizza-place.ts:
--------------------------------------------------------------------------------
1 | import { PizzaPlace, DiningTables } from "../stores/store.pizza-place"
2 | import type { FatLib2Container } from "./container.fat-lib2"
3 |
4 | export interface PizzaPlace_Container {
5 | pizzaPlace: PizzaPlace
6 | diningTables: DiningTables
7 | }
8 |
9 | export async function providePizzaPlaceContainer(
10 | fatLib2Container: FatLib2Container,
11 | ): Promise {
12 | const a1 = new PizzaPlace(fatLib2Container.getFatLib2Data)
13 | const a2 = new DiningTables()
14 |
15 | return {
16 | pizzaPlace: a1,
17 | diningTables: a2,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/cra/src/containers/my-jest.ts:
--------------------------------------------------------------------------------
1 | export function itAsyncDone(
2 | name: string,
3 | cb: (done: jest.DoneCallback) => Promise,
4 | timeout?: number,
5 | ) {
6 | it(
7 | name,
8 | (done) => {
9 | let doneCalled = false
10 | const wrappedDone: jest.DoneCallback = (...args) => {
11 | if (doneCalled) {
12 | return
13 | }
14 |
15 | doneCalled = true
16 | done(...args)
17 | }
18 |
19 | wrappedDone.fail = (err) => {
20 | if (doneCalled) {
21 | return
22 | }
23 |
24 | doneCalled = true
25 |
26 | done.fail(err)
27 | }
28 |
29 | cb(wrappedDone).catch(wrappedDone)
30 | },
31 | timeout,
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/examples/cra/src/fat-libs/fat-lib1.ts:
--------------------------------------------------------------------------------
1 | export function fatLib300kb() {
2 | console.log("getting 300kb lib")
3 | return "300KB fatlib string"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/cra/src/fat-libs/fat-lib2.ts:
--------------------------------------------------------------------------------
1 | export class FatLib2_3MB {
2 | public getData() {
3 | console.log("getting 3MB lib")
4 | return "3 MB fatlib string"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/cra/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/cra/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { createRoot } from "react-dom/client"
3 | import "./index.css"
4 | import App from "./App"
5 | import reportWebVitals from "./reportWebVitals"
6 |
7 | const container = document.getElementById("root")
8 | const root = createRoot(container!) // createRoot(container!) if you use TypeScript
9 | root.render(
10 |
11 |
12 | ,
13 | )
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 | reportWebVitals()
19 |
--------------------------------------------------------------------------------
/examples/cra/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/cra/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/examples/cra/src/services/ingredients-manager.ts:
--------------------------------------------------------------------------------
1 | import { Ingredients } from "../stores/store.ingrediets"
2 | import _sampleSize from "lodash/sampleSize"
3 | import _sample from "lodash/sample"
4 |
5 | const INGREDIENTS = [
6 | "tomatoes",
7 | "olives",
8 | "salami",
9 | "cheese",
10 | "mushrooms",
11 | "bell pepper",
12 | "mozzarella",
13 | ] as const
14 |
15 | /**
16 | * Fetches Ingredients from the supermarket
17 | */
18 | export class IngredientsService {
19 | public static async buyManyIngredients(): Promise {
20 | return this.buyIngredients(150)
21 | }
22 |
23 | public static async buySomeIngredients(): Promise {
24 | return this.buyIngredients(30)
25 | }
26 |
27 | private static buyIngredients(n = 10): Ingredients {
28 | let x = new Ingredients()
29 |
30 | for (let i = n; i > 0; i--) {
31 | let z = _sample(INGREDIENTS)
32 | if (z != null) {
33 | x.addNewIngredient(z)
34 | }
35 | }
36 | return x
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/cra/src/services/url-param.ts:
--------------------------------------------------------------------------------
1 | export type DayType = "normal" | "friday" | "weekend"
2 |
3 | export function getDayType(): DayType {
4 | const uri = new URL(window.location.href)
5 |
6 | if (uri.searchParams.has("dayType")) {
7 | const mode = uri.searchParams.get("dayType")
8 |
9 | if (mode === "friday") {
10 | return "friday"
11 | }
12 |
13 | if (mode === "weekend") {
14 | return "weekend"
15 | }
16 | }
17 |
18 | return "normal"
19 | }
20 |
21 | export type AuthType = "unauthenticated" | "manager" | "admin"
22 |
23 | export function getAuthType(): AuthType {
24 | const uri = new URL(window.location.href)
25 |
26 | if (uri.searchParams.has("authType")) {
27 | const mode = uri.searchParams.get("authType")
28 |
29 | if (mode === "manager") {
30 | return "manager"
31 | }
32 |
33 | if (mode === "admin") {
34 | return "admin"
35 | }
36 | }
37 |
38 | return "unauthenticated"
39 | }
40 | export function setAuthType(at: AuthType) {
41 | const url = new URL(window.location.href)
42 | url.searchParams.set("authType", at)
43 | console.log("changing user and url to", at)
44 | window.history.pushState({ authType: at }, at, url)
45 | }
46 |
--------------------------------------------------------------------------------
/examples/cra/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/_controllers/controller.kitchen.ts:
--------------------------------------------------------------------------------
1 | export class KitchenSizeUIController {
2 | constructor(
3 | private cbs: {
4 | onKitchenResize: () => Promise
5 | },
6 | ) {}
7 |
8 | public async increaseKitchenSize() {
9 | console.log("kitchen resize")
10 | this.cbs.onKitchenResize()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.a.ts:
--------------------------------------------------------------------------------
1 | import type { Auth } from "./store.auth"
2 |
3 | export class A1 {
4 | constructor(private auth: Auth) {}
5 | public getName() {
6 | return "Jon Snow"
7 | }
8 | }
9 | export class A2 {
10 | constructor(private a1: A1, private auth: Auth) {}
11 | }
12 | export class A3 {
13 | constructor(private a1: A1, private a2: A2) {}
14 | }
15 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.auth.ts:
--------------------------------------------------------------------------------
1 | import { getAuthType, setAuthType, AuthType } from "../services/url-param"
2 |
3 | const wait = (w: number) => new Promise((r) => setTimeout(r, w))
4 |
5 | export class Auth {
6 | public async getToken(): Promise {
7 | await wait(200)
8 | return "token123"
9 | }
10 |
11 | public async getUser(): Promise {
12 | await wait(200)
13 | return { name: "lol" }
14 | }
15 |
16 | public async getUserType() {
17 | await wait(500)
18 | const t = getAuthType()
19 | return t
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.authorization.ts:
--------------------------------------------------------------------------------
1 | import { makeObservable, observable, computed, action, autorun } from "mobx"
2 | import { getAuthType, setAuthType, AuthType } from "../services/url-param"
3 |
4 | const wait = (w: number) => new Promise((r) => setTimeout(r, w))
5 |
6 | const RightsDB = {
7 | unauthenticated: {
8 | orderPizza: true,
9 | addTables: false,
10 | upgradeKitchen: false,
11 | },
12 | manager: {
13 | orderPizza: true,
14 | addTables: true,
15 | upgradeKitchen: false,
16 | },
17 | admin: {
18 | orderPizza: true,
19 | addTables: true,
20 | upgradeKitchen: true,
21 | },
22 | } as const
23 |
24 | function rightsLookup(rights: T) {
25 | return rights
26 | }
27 |
28 | export class Authorization {
29 | constructor(private _userType: AuthType) {
30 | makeObservable(this, {
31 | // @ts-expect-error
32 | _userType: observable,
33 | userType: computed,
34 | changeUser: action,
35 | })
36 | }
37 |
38 | get userType() {
39 | return this._userType
40 | }
41 |
42 | /**
43 | * this API might look weird but this helps typescript
44 | * k.getAvailableActions()["admin"]
45 | */
46 | public getAvailableActions() {
47 | return rightsLookup(RightsDB)
48 | }
49 |
50 | public async getToken(): Promise {
51 | await wait(200)
52 | return "token123"
53 | }
54 |
55 | public async getUser(): Promise {
56 | await wait(200)
57 | return { name: "lol" }
58 | }
59 |
60 | public async getUserType() {
61 | await wait(500)
62 | const t = getAuthType
63 | return t
64 | }
65 |
66 | public async changeUser(at: AuthType) {
67 | await wait(400)
68 | this._userType = at
69 | setAuthType(at)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.b.ts:
--------------------------------------------------------------------------------
1 | import type { A1, A2 } from "./store.a"
2 | import type { Auth } from "./store.auth"
3 |
4 | export class B1 {
5 | constructor(private auth: Auth, private a2: A2) {
6 | // 1. Subscribe to event emitter
7 | // 2. Pub update
8 | }
9 | }
10 | export class B2 {
11 | constructor(private auth: Auth, private a1: A1) {}
12 | }
13 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.ingrediets.ts:
--------------------------------------------------------------------------------
1 | import {
2 | makeAutoObservable,
3 | makeObservable,
4 | action,
5 | computed,
6 | observable,
7 | } from "mobx"
8 | import _sample from "lodash/sample"
9 | import _pullAt from "lodash/pullAt"
10 | import _countBy from "lodash/countBy"
11 |
12 | export class Ingredients {
13 | public ingredients: Ingredient[] = []
14 |
15 | constructor() {
16 | makeObservable(this, {
17 | ingredients: observable,
18 | addNewIngredient: action,
19 | getRandomPizzaIngredients: action,
20 | ingredientsStats: computed,
21 | })
22 | }
23 |
24 | public addNewIngredient(n: string) {
25 | this.ingredients.push(new Ingredient(n))
26 | }
27 |
28 | public getRandomPizzaIngredients() {
29 | let pi: Ingredient[] = []
30 |
31 | let k = 4
32 | while (k > 0) {
33 | const i = Math.floor(Math.random() * this.ingredients.length)
34 | pi.push(this.ingredients[i])
35 | _pullAt(this.ingredients, i)
36 | k--
37 | }
38 | return pi
39 | }
40 |
41 | public get ingredientsStats() {
42 | const stats = _countBy(this.ingredients, (ing) => ing.name)
43 | return Object.entries(stats)
44 | }
45 | }
46 |
47 | export class Ingredient {
48 | constructor(public name: string) {
49 | makeAutoObservable(this)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.kitchen.ts:
--------------------------------------------------------------------------------
1 | import { makeAutoObservable } from "mobx"
2 | import type { Ingredients } from "./store.ingrediets"
3 | import type { Oven } from "./store.oven"
4 | import { Pizza } from "./store.pizza"
5 | import type { Table } from "./store.pizza-place"
6 | import debug from "debug"
7 | const log = debug("oven")
8 |
9 | export class Kitchen {
10 | public kitchenName: string
11 |
12 | constructor(private oven: Oven, private ingredients: Ingredients) {
13 | this.kitchenName = "Random Name " + Math.round(Math.random() * 100)
14 | log("new kitchen: " + this.kitchenName)
15 |
16 | makeAutoObservable(this)
17 | }
18 |
19 | public getRandomPizzaIngredients() {
20 | return this.ingredients.getRandomPizzaIngredients()
21 | }
22 |
23 | public async bakePizza(p: Pizza) {
24 | return this.oven.bakePizza(p)
25 | }
26 | }
27 |
28 | export class OrderManager {
29 | public orders: Order[] = []
30 |
31 | constructor(private kitchen: Kitchen) {
32 | makeAutoObservable(this)
33 | }
34 |
35 | public async orderPizza(table: Table) {
36 | let i = this.kitchen.getRandomPizzaIngredients()
37 | let p = new Pizza(i)
38 |
39 | this.orders.push(new Order(p, table))
40 |
41 | this.kitchen.bakePizza(p)
42 | }
43 | }
44 |
45 | class Order {
46 | constructor(public pizza: Pizza, public table: Table) {}
47 | }
48 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.oven.ts:
--------------------------------------------------------------------------------
1 | import { makeAutoObservable, computed } from "mobx"
2 | import type { Pizza } from "./store.pizza"
3 | import debug from "debug"
4 | const log = debug("oven")
5 |
6 | const BAKING_TIME_MS = 400
7 | const BAKING_TEMPERATURE = 260
8 |
9 | export class Oven {
10 | public currentTemperature = 20
11 | public pizInside: Pizza[] = []
12 |
13 | constructor(private _pizzaCapacity = 4) {
14 | log("new Oven. capacity:", this._pizzaCapacity)
15 | makeAutoObservable(this)
16 | }
17 | public get pizzaCapacity() {
18 | return this._pizzaCapacity
19 | }
20 |
21 | public async preheatOven() {
22 | setTimeout(() => {
23 | this.updateTemperature(BAKING_TEMPERATURE)
24 | }, 200)
25 | }
26 |
27 | public async bakePizza(pizza: Pizza) {
28 | if (this.currentTemperature < BAKING_TEMPERATURE) {
29 | await this.preheatOven()
30 | }
31 |
32 | if (this.pizzasInOven() < this._pizzaCapacity) {
33 | this.addPizzaToOven(pizza)
34 | setTimeout(() => {
35 | pizza.updatePizzaState("baked")
36 | }, BAKING_TIME_MS)
37 | }
38 | }
39 |
40 | private updateTemperature(nc: number) {
41 | this.currentTemperature = nc
42 | }
43 |
44 | private addPizzaToOven(p: Pizza) {
45 | this.pizInside.push(p)
46 | }
47 |
48 | @computed
49 | public pizzasInOven() {
50 | return this.pizInside.length
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.pizza-place.ts:
--------------------------------------------------------------------------------
1 | import { makeObservable, makeAutoObservable, observable, action } from "mobx"
2 | import type { FatLib2_3MB } from "../fat-libs/fat-lib2"
3 |
4 | export class PizzaPlace {
5 | public isOpen = false
6 | public name = "Rocket Pizza"
7 |
8 | constructor(private fatlib: () => Promise) {
9 | console.log("new pizza place")
10 | // makeAutoObservable(this)
11 | makeObservable(this, {
12 | isOpen: observable,
13 | openPizzaPlace: action,
14 | closePizzaPlace: action,
15 | })
16 | }
17 |
18 | public openPizzaPlace() {
19 | this.isOpen = true
20 | }
21 |
22 | public closePizzaPlace() {
23 | this.isOpen = false
24 | }
25 |
26 | public async getFatLibImage() {
27 | const fatLib = await this.fatlib()
28 | return fatLib.getData()
29 | }
30 | }
31 |
32 | export class DiningTables {
33 | public tables: Table[] = []
34 |
35 | constructor() {
36 | makeAutoObservable(this)
37 | }
38 |
39 | public addNewTable() {
40 | console.log("adding new table")
41 | const name = (this.tables.length + 1).toString()
42 | this.tables.push(new Table(name))
43 | }
44 | }
45 |
46 | export class Table {
47 | isEmpty = true
48 | constructor(public name: string) {
49 | makeAutoObservable(this)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/cra/src/stores/store.pizza.ts:
--------------------------------------------------------------------------------
1 | import { makeAutoObservable } from "mobx"
2 | import type { Ingredient } from "./store.ingrediets"
3 |
4 | type PizzaState = "raw" | "hasIngredients" | "baked"
5 |
6 | export class Pizza {
7 | public state: PizzaState = "raw"
8 |
9 | constructor(public ingredients: Ingredient[]) {
10 | console.log("new pizza with ingredients created")
11 | makeAutoObservable(this)
12 | }
13 |
14 | public updatePizzaState(ns: PizzaState) {
15 | this.state = ns
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/cra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "noImplicitAny": false,
10 | "experimentalDecorators": true,
11 | "allowJs": true,
12 | "skipLibCheck": true,
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "strict": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "module": "esnext",
19 | "moduleResolution": "node",
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "noEmit": true,
23 | "jsx": "react-jsx"
24 | },
25 | "include": [
26 | "src"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/node-cjs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn-error.log
3 | dist
--------------------------------------------------------------------------------
/examples/node-cjs/node-1.js:
--------------------------------------------------------------------------------
1 | const iti = require("iti")
2 |
3 | const container = iti
4 | .createContainer()
5 | .add({
6 | doggoName: "Moon Moon",
7 | })
8 | .add((c) => ({
9 | sayName: () => console.log("Doggo's name is " + c.doggoName),
10 | }))
11 | let a = container.items.sayName
12 | let b = container.items.doggoName
13 |
14 | console.log(a, b)
15 |
--------------------------------------------------------------------------------
/examples/node-cjs/node-2.js:
--------------------------------------------------------------------------------
1 | const { createContainer } = require("iti")
2 |
3 | function Doggo(name) {
4 | return {
5 | greetText: "Doggo name is " + name,
6 | }
7 | }
8 |
9 | let root = createContainer()
10 | .add({
11 | doggoName: "Moon Moon",
12 | })
13 | .add((c) => ({
14 | doggo: () => new Doggo(c.doggoName),
15 | }))
16 | .add((c) => ({
17 | sayName: () => {
18 | console.log(c.doggo.greetText)
19 | },
20 | }))
21 | root.get("sayName")
22 |
--------------------------------------------------------------------------------
/examples/node-cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-node-example-cjs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "debug": "^4.3.3",
7 | "lodash": "^4.17.21",
8 | "mobx": "^6.3.12",
9 | "nodemon": "^2.0.15",
10 | "iti": "0.7.0",
11 | "react": "^17.0.2",
12 | "ts-node": "^10.4.0",
13 | "typescript": "^4.5.5"
14 | },
15 | "scripts": {
16 | "start": "node ./dist/server",
17 | "watch": "tsc -w",
18 | "serverWatch": "nodemon -x 'clear;node' ./dist/server"
19 | },
20 | "license": "MIT",
21 | "authors": [
22 | "Nick Olszanski "
23 | ],
24 | "prettier": {
25 | "semi": false,
26 | "singleQuote": false,
27 | "arrowParens": "always",
28 | "trailingComma": "all"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/node-cli/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn-error.log
3 | dist
--------------------------------------------------------------------------------
/examples/node-cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-node-example",
3 | "type": "module",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "debug": "^4.3.3",
8 | "lodash": "^4.17.21",
9 | "mobx": "^6.3.12",
10 | "nodemon": "^2.0.15",
11 | "iti": "0.7.0",
12 | "react": "^17.0.2",
13 | "ts-node": "^10.4.0",
14 | "typescript": "^4.5.5"
15 | },
16 | "scripts": {
17 | "start": "node ./dist/server",
18 | "watch": "tsc -w",
19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server"
20 | },
21 | "license": "MIT",
22 | "authors": [
23 | "Nick Olszanski "
24 | ],
25 | "prettier": {
26 | "semi": false,
27 | "singleQuote": false,
28 | "arrowParens": "always",
29 | "trailingComma": "all"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/node-cli/src/server.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash"
2 | import { createContainer } from "iti"
3 |
4 | // Step 1: Your application logic stays clean
5 | class Oven {
6 | public pizzasInOven() {
7 | return 3
8 | }
9 | public async preheat() {}
10 | }
11 | class Kitchen {
12 | constructor(public oven: Oven, public manual: string) {}
13 | }
14 |
15 | // Step 2: Add and read simple tokens
16 | let root = createContainer().add({
17 | userManual: "Please preheat before use",
18 | oven: () => new Oven(),
19 | })
20 | root.get("oven")
21 |
22 | // Step 3: Add a useful async provider / container
23 | const kitchenContainer = async ({ oven, userManual }) => {
24 | await oven.preheat()
25 | return {
26 | kitchen: new Kitchen(oven, userManual),
27 | }
28 | }
29 |
30 | // Step 4: Add an async provider
31 | const node = root.add((ctx, node) => ({
32 | kitchen: async () =>
33 | kitchenContainer(await node.getContainerSet(["userManual", "oven"])),
34 | }))
35 | await node.get("kitchen")
36 |
37 | // A SHORT USE MANUAL
38 | // A SHORT USE MANUAL
39 | // A SHORT USE MANUAL
40 |
41 | // ---- Reading
42 |
43 | // Get a single instance
44 | root.get("oven") // Creates a new Oven instance
45 | root.get("oven") // Gets a cached Oven instance
46 |
47 | await node.get("kitchen") // { kitchen: Kitchen } also cached
48 | await node.items.kitchen // same as above
49 |
50 | // Get multiple instances at once
51 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven }
52 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above
53 |
54 | // Subscribe to container changes
55 | node.subscribeToContainer("oven", (oven) => {})
56 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {})
57 | // prettier-ignore
58 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {})
59 | node.on("containerUpdated", ({ key, newContainer }) => {})
60 | node.on("containerUpserted", ({ key, newContainer }) => {})
61 |
62 | // ----Adding
63 |
64 | let node1 = createContainer()
65 | .add({
66 | userManual: "Please preheat before use",
67 | oven: () => new Oven(),
68 | })
69 | .upsert((ctx) => ({
70 | userManual: "Works better when hot",
71 | preheatedOven: async () => {
72 | await ctx.oven.preheat()
73 | return ctx.oven
74 | },
75 | }))
76 |
77 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert`
78 | try {
79 | node1.add({
80 | // @ts-expect-error
81 | userManual: "You shall not pass",
82 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method"
83 | })
84 | } catch (err) {
85 | err.message // Error Tokens already exist: ['userManual']
86 | }
87 |
88 | // // async function runStuff() {
89 | // // let a = new AppContainer()
90 |
91 | // // let k = await a.getKitchenContainer()
92 | // // let pp = await a.getPizzaPlaceContainer()
93 |
94 | // // pp.diningTables.addNewTable()
95 | // // pp.diningTables.addNewTable()
96 | // // pp.diningTables.addNewTable()
97 |
98 | // // k.orderManager.orderPizza(pp.diningTables.tables[1])
99 | // // k.orderManager.orderPizza(pp.diningTables.tables[2])
100 |
101 | // // console.log(k.orderManager.orders)
102 | // // k.orderManager.orders.forEach((order) => {
103 | // // console.log(order.pizza.state)
104 | // // console.log(JSON.stringify(order.pizza.ingredients))
105 | // // })
106 | // // }
107 |
108 | // // runStuff().then(() => {
109 | // // console.log("done")
110 | // // })
111 |
112 | // export const x = { a: 12 }
113 |
--------------------------------------------------------------------------------
/examples/node-cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types"],
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "target": "esnext",
6 | "outDir": "dist",
7 | "moduleResolution": "node",
8 | "jsx": "preserve",
9 | "baseUrl": "./",
10 | /* Additional Options */
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "resolveJsonModule": true,
14 | "allowSyntheticDefaultImports": true,
15 | "experimentalDecorators": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/node-js/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn-error.log
3 | dist
--------------------------------------------------------------------------------
/examples/node-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-node-example",
3 | "type": "module",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "debug": "^4.3.3",
8 | "lodash": "^4.17.21",
9 | "mobx": "^6.3.12",
10 | "nodemon": "^2.0.15",
11 | "iti": "0.7.0",
12 | "react": "^17.0.2",
13 | "ts-node": "^10.4.0",
14 | "typescript": "^4.5.5"
15 | },
16 | "scripts": {
17 | "start": "node ./dist/server",
18 | "watch": "tsc -w",
19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server"
20 | },
21 | "license": "MIT",
22 | "authors": [
23 | "Nick Olszanski "
24 | ],
25 | "prettier": {
26 | "semi": false,
27 | "singleQuote": false,
28 | "arrowParens": "always",
29 | "trailingComma": "all"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/node-js/src/server.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash"
2 | import { createContainer } from "iti"
3 |
4 | // Step 1: Your application logic stays clean
5 | class Oven {
6 | public pizzasInOven() {
7 | return 3
8 | }
9 | public async preheat() {}
10 | }
11 | class Kitchen {
12 | constructor(public oven: Oven, public manual: string) {}
13 | }
14 |
15 | // Step 2: Add and read simple tokens
16 | let root = createContainer().add({
17 | userManual: "Please preheat before use",
18 | oven: () => new Oven(),
19 | })
20 | root.get("oven")
21 |
22 | // Step 3: Add a useful async provider / container
23 | const kitchenContainer = async ({ oven, userManual }) => {
24 | await oven.preheat()
25 | return {
26 | kitchen: new Kitchen(oven, userManual),
27 | }
28 | }
29 |
30 | // Step 4: Add an async provider
31 | const node = root.add((ctx, node) => ({
32 | kitchen: async () =>
33 | kitchenContainer(await node.getContainerSet(["userManual", "oven"])),
34 | }))
35 | await node.get("kitchen")
36 |
37 | // A SHORT USE MANUAL
38 | // A SHORT USE MANUAL
39 | // A SHORT USE MANUAL
40 |
41 | // ---- Reading
42 |
43 | // Get a single instance
44 | root.get("oven") // Creates a new Oven instance
45 | root.get("oven") // Gets a cached Oven instance
46 |
47 | await node.get("kitchen") // { kitchen: Kitchen } also cached
48 | await node.items.kitchen // same as above
49 |
50 | // Get multiple instances at once
51 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven }
52 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above
53 |
54 | // Subscribe to container changes
55 | node.subscribeToContainer("oven", (oven) => {})
56 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {})
57 | // prettier-ignore
58 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {})
59 | node.on("containerUpdated", ({ key, newContainer }) => {})
60 | node.on("containerUpserted", ({ key, newContainer }) => {})
61 |
62 | // ----Adding
63 |
64 | let node1 = createContainer()
65 | .add({
66 | userManual: "Please preheat before use",
67 | oven: () => new Oven(),
68 | })
69 | .upsert((ctx) => ({
70 | userManual: "Works better when hot",
71 | preheatedOven: async () => {
72 | await ctx.oven.preheat()
73 | return ctx.oven
74 | },
75 | }))
76 |
77 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert`
78 | try {
79 | node1.add({
80 | // @ts-expect-error
81 | userManual: "You shall not pass",
82 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method"
83 | })
84 | } catch (err) {
85 | err.message // Error Tokens already exist: ['userManual']
86 | }
87 |
88 | // // async function runStuff() {
89 | // // let a = new AppContainer()
90 |
91 | // // let k = await a.getKitchenContainer()
92 | // // let pp = await a.getPizzaPlaceContainer()
93 |
94 | // // pp.diningTables.addNewTable()
95 | // // pp.diningTables.addNewTable()
96 | // // pp.diningTables.addNewTable()
97 |
98 | // // k.orderManager.orderPizza(pp.diningTables.tables[1])
99 | // // k.orderManager.orderPizza(pp.diningTables.tables[2])
100 |
101 | // // console.log(k.orderManager.orders)
102 | // // k.orderManager.orders.forEach((order) => {
103 | // // console.log(order.pizza.state)
104 | // // console.log(JSON.stringify(order.pizza.ingredients))
105 | // // })
106 | // // }
107 |
108 | // // runStuff().then(() => {
109 | // // console.log("done")
110 | // // })
111 |
112 | // export const x = { a: 12 }
113 |
--------------------------------------------------------------------------------
/examples/node-mjs/node-1.mjs:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 |
3 | const container = createContainer()
4 | .add({
5 | doggoName: "Moon Moon",
6 | })
7 | .add((c) => ({
8 | sayName: () => console.log("Doggo's name is " + c.doggoName),
9 | }))
10 | container.items.sayName
11 |
--------------------------------------------------------------------------------
/examples/node-mjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-node-example-mjs",
3 | "type": "module",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "debug": "^4.3.3",
8 | "lodash": "^4.17.21",
9 | "mobx": "^6.3.12",
10 | "nodemon": "^2.0.15",
11 | "iti": "0.7.0",
12 | "react": "^17.0.2",
13 | "ts-node": "^10.4.0",
14 | "typescript": "^4.5.5"
15 | },
16 | "scripts": {
17 | "start": "node ./dist/server",
18 | "watch": "tsc -w",
19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server"
20 | },
21 | "license": "MIT",
22 | "authors": [
23 | "Nick Olszanski "
24 | ],
25 | "prettier": {
26 | "semi": false,
27 | "singleQuote": false,
28 | "arrowParens": "always",
29 | "trailingComma": "all"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/vite-app/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vite-app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/vite-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-vite-example",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "iti": "0.7.0",
13 | "iti-react": "0.7.0",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.0.15",
19 | "@types/react-dom": "^18.0.6",
20 | "@vitejs/plugin-react": "^2.0.0",
21 | "typescript": "^4.6.4",
22 | "vite": "^3.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/vite-app/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vite-app/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/examples/vite-app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import reactLogo from "./assets/react.svg"
3 | import "./App.css"
4 | import { MyAppContext, useContainer } from "./hooks"
5 | import { app } from "./_bl"
6 |
7 | const Lol = () => {
8 | const [b, bErr] = useContainer().b
9 |
10 | console.log("render", b, bErr)
11 | return 123
12 | }
13 |
14 | function App() {
15 | const [count, setCount] = useState(0)
16 |
17 | return (
18 |
19 |
20 |
21 |
29 |
Vite + React
30 |
31 |
34 |
35 | Edit src/App.tsx
and save to test HMR
36 |
37 |
38 |
39 | Click on the Vite and React logos to learn more
40 |
41 |
42 |
43 | )
44 | }
45 |
46 | export default App
47 |
--------------------------------------------------------------------------------
/examples/vite-app/src/_bl.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 |
3 | export class A {}
4 | export class B {
5 | constructor(a: A) {}
6 | }
7 | export class C {
8 | constructor(a: A, b: B) {}
9 | }
10 |
11 | export const app = createContainer()
12 | .add(() => ({
13 | a: () => new A(),
14 | }))
15 | .add((ctx) => ({
16 | b: async () => new B(ctx.a),
17 | }))
18 | .add((ctx) => ({
19 | c: () => new C(ctx.a, ctx.b),
20 | }))
21 |
--------------------------------------------------------------------------------
/examples/vite-app/src/hooks.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { getContainerSetHooks } from "iti-react"
3 | import { app } from "./_bl"
4 |
5 | export const MyAppContext = React.createContext({} as any)
6 |
7 | const hooks = getContainerSetHooks(MyAppContext)
8 | export const useContainerSet = hooks.useContainerSet
9 | export const useContainer = hooks.useContainer
10 |
--------------------------------------------------------------------------------
/examples/vite-app/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/examples/vite-app/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/examples/vite-app/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/vite-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/examples/vite-app/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/vite-app/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/iti-react/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | yarn-error.log
4 | coverage
--------------------------------------------------------------------------------
/iti-react/jest.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | /** @type {import('@types/jest')} */
3 | module.exports = {
4 | preset: "ts-jest/presets/js-with-ts-esm",
5 | testEnvironment: "jsdom",
6 | testTimeout: 500,
7 | }
8 |
--------------------------------------------------------------------------------
/iti-react/notes-and-ideas.md:
--------------------------------------------------------------------------------
1 | ### lookup table
2 |
3 | For runtime optimizations of the search in async nodes we can provide tokens as a second argument in an `addPromise `
4 |
5 | ```ts
6 | let n = createContainer()
7 | .addNode({ a: 1, b: 2 })
8 | .addPromise(
9 | async (c) => {
10 | return { c: 3, d: 4 }
11 | },
12 | ["c", "d"], // <-- this might be a purely runtime optimization to index the lookup and make code even more lazy
13 | )
14 | .addPromiseStrict(
15 | // Or even better add a new method
16 | async (c) => {
17 | return { c: 3, d: 4 }
18 | },
19 | ["c", "d"], // that forces you to list all deps keys and TS can actually check it!!!
20 | )
21 | ```
22 |
23 | ### Nano emitter
24 |
25 | try nano emitter from evil martians but check if they support multiple subscribes gracefully via `node lol.js`
26 |
27 | ### Disable promise for `addNode`
28 |
29 | add node should TS throw if pass async. TYpescirpt should lookup return type and dissallow promise return type
30 |
31 | ### Disable overrides for `addNode`
32 |
33 | we can TS throw if we see that user has provided a duplicate token.
34 | Dublicate tokens maybe then could be added via `overrideNode`
35 |
36 | ### Add options for `addNode`
37 |
38 | - first option is a lookup table of tokens
39 | - second idea would be a force override parameter when you want to force flush changes
40 |
--------------------------------------------------------------------------------
/iti-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-react",
3 | "version": "0.7.0",
4 | "description": "Handy React bindings for iti, a ~1kB Typesafe dependency injection framework for TypeScript and JavaScript with a unique support for async flow",
5 | "type": "module",
6 | "sideEffects": false,
7 | "module": "./dist/iti-react.js",
8 | "typings": "./dist/index.d.ts",
9 | "types": "./dist/index.d.ts",
10 | "exports": {
11 | ".": {
12 | "import": {
13 | "types": "./dist/index.d.ts",
14 | "default": "./dist/iti-react.js"
15 | },
16 | "default": "./dist/iti-react.js"
17 | },
18 | "./package.json": "./package.json"
19 | },
20 | "source": "src/index.ts",
21 | "publishConfig": {
22 | "source": "./src/index.ts",
23 | "module": "./dist/iti-react.js"
24 | },
25 | "files": [
26 | "dist"
27 | ],
28 | "scripts": {
29 | "build": "rm -rf ./dist && microbundle -o dist/ --jsx React.createElement --format modern",
30 | "dev": "microbundle watch -o dist/ --jsx React.createElement",
31 | "test": "yarn tsd",
32 | "tsd": "tsd tsd_project",
33 | "prepare-not": "install-peers"
34 | },
35 | "dependencies": {
36 | "iti": "0.7.0",
37 | "utility-types": "^3.10.0"
38 | },
39 | "devDependencies": {
40 | "@types/react": "^18.0.15",
41 | "install-peers-cli": "^2.2.0",
42 | "microbundle": "^0.15.1",
43 | "nodemon": "^2.0.19",
44 | "tsd": "^0.22.0",
45 | "typescript": "^4.7.4"
46 | },
47 | "peerDependencies": {
48 | "react": ">=16",
49 | "react-dom": ">=17.0.2"
50 | },
51 | "repository": "https://github.com/molszanski/iti",
52 | "homepage": "https://itijs.org",
53 | "author": "Nick Olszanski",
54 | "license": "MIT",
55 | "prettier": {
56 | "semi": false,
57 | "singleQuote": false,
58 | "arrowParens": "always",
59 | "trailingComma": "all"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/iti-react/src/API.md:
--------------------------------------------------------------------------------
1 | For container set there where a couple of options.
2 | I coded option 1 because it was easy.
3 |
4 | But now I want option
5 |
6 | ```js
7 | // Option 1
8 | let containerSet1 = await cont.getContainerSet(["aCont", "bCont", "cCont"])
9 |
10 | // Option 2
11 | let containerSet2 = await cont.getContainerSet((c) => [
12 | c.aCont,
13 | c.bCont,
14 | c.Cont,
15 | ])
16 |
17 | // Option 3
18 | let containerSet2 = await cont.getContainerSet(({ aCont, bCont, cCont }) => ({
19 | aCont,
20 | bCont,
21 | cCont,
22 | }))
23 |
24 | // Option 4
25 | let c = cont.tokens
26 | let containerSet2 = await cont.getContainerSet([c.aCont, c.bCont, c.Cont])
27 | ```
28 |
--------------------------------------------------------------------------------
/iti-react/src/_utils.ts:
--------------------------------------------------------------------------------
1 | export type UnPromisify = T extends Promise ? U : T
2 |
3 | export type GetContainerFormat any> =
4 | UnPromisify>
5 |
6 | export function addGetter(object, key, fn: any) {
7 | Object.defineProperty(object, key, {
8 | get() {
9 | return fn()
10 | },
11 | enumerable: true,
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/iti-react/src/index.ts:
--------------------------------------------------------------------------------
1 | // export { createContainer, RootContainer } from "./library.root-container"
2 | export type { GetContainerFormat, UnPromisify } from "./_utils"
3 |
4 | // React
5 | export { generateEnsureContainerSet } from "./react/library.generate-ensure-hooks.js"
6 | export { getContainerSetHooks } from "./react/library.hook-generator.js"
7 |
--------------------------------------------------------------------------------
/iti-react/src/react/library.generate-ensure-hooks.ts:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react"
2 |
3 | export function generateEnsureContainerSet(
4 | containerSetGetterHook: (...args: any) => [ContainerSetContext, any],
5 | ) {
6 | const EnsureReactContext = React.createContext({} as any)
7 |
8 | function useThatContext() {
9 | return useContext(EnsureReactContext)
10 | }
11 |
12 | const EnsureContainer = (props: {
13 | fallback?: JSX.Element
14 | children: React.ReactNode
15 | }) => {
16 | let [containerSet, err] = containerSetGetterHook()
17 | if (!containerSet || err != null) {
18 | if (props.fallback) {
19 | return props.fallback
20 | } else {
21 | return null
22 | }
23 | }
24 |
25 | return React.createElement(
26 | EnsureReactContext.Provider,
27 | { value: containerSet },
28 | props.children,
29 | )
30 | }
31 |
32 | return {
33 | EnsureWrapper: EnsureContainer,
34 | contextHook: useThatContext,
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/iti-react/src/react/library.hook-generator.ts:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react"
2 | import { useBetterGenericContainer } from "./library.hooks.js"
3 | import { addGetter } from "../_utils.js"
4 |
5 | import type { UnPromisify } from "../_utils"
6 | import type { Container, UnpackFunction, Prettify } from "iti"
7 |
8 | type UnpackTokenFromContext<
9 | CK extends keyof Context,
10 | Context extends {},
11 | > = UnPromisify>
12 |
13 | type ContainerSet = {
14 | [S in Tokens]: UnpackTokenFromContext
15 | }
16 |
17 | export function getContainerSetHooks<
18 | Context extends object,
19 | DisposeContext extends object,
20 | >(reactContext: React.Context>) {
21 | function useContainer() {
22 | const root = useContext(reactContext)
23 | return useRootStores(root)
24 | }
25 |
26 | function useRootStores<
27 | /**
28 | * Basically a nice api for hooks
29 | * {
30 | * name: () => [containerInstance, err ]
31 | * }
32 | */
33 | ContainerGetter extends {
34 | [CK in keyof Context]: Context[CK] extends any
35 | ? [UnpackTokenFromContext | undefined, any, CK]
36 | : never
37 | },
38 | >(appRoot: Container): ContainerGetter {
39 | let FFF = {}
40 | let tokens = appRoot.getTokens()
41 |
42 | for (let contKey in tokens) {
43 | addGetter(FFF, contKey, () =>
44 | useBetterGenericContainer(
45 | () => appRoot.containers[contKey as any],
46 | // @ts-expect-error
47 | (cb: () => any) => appRoot.subscribeToContainer(contKey, cb),
48 | contKey,
49 | ),
50 | )
51 | }
52 |
53 | return FFF
54 | }
55 |
56 | function useContainerSet<
57 | Tokens extends keyof Context,
58 | TokenMap extends { [T in keyof Context]: T },
59 | >(
60 | tokensOrCallback: Tokens[] | ((keyMap: TokenMap) => Tokens[]),
61 | ): [Prettify>, any] {
62 | const [all, setAll] = useState>(
63 | undefined as any,
64 | )
65 | const [err, setErr] = useState(undefined as any)
66 | const root = useContext(reactContext)
67 |
68 | // WIP
69 | const tokens =
70 | typeof tokensOrCallback === "function"
71 | ? root._extractTokens(tokensOrCallback as any)
72 | : tokensOrCallback
73 |
74 | useEffect(() => {
75 | root.getContainerSet(tokens).then((contSet) => {
76 | setAll(contSet)
77 | })
78 | }, tokens)
79 |
80 | useEffect(() => {
81 | const unsubscribe = root.subscribeToContainerSet(
82 | tokens,
83 | (err, contSet) => {
84 | if (err) {
85 | setErr(err)
86 | return
87 | }
88 | setAll(contSet)
89 | },
90 | )
91 | return unsubscribe
92 | }, tokens)
93 |
94 | return [all as any, err]
95 | }
96 | return {
97 | useContainer: useContainer,
98 | useContainerSet: useContainerSet,
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/iti-react/src/react/library.hooks.ts:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react"
2 | // -- Generic
3 |
4 | export type ContainerGeneric = {
5 | container?: T
6 | error?: Error
7 | key?: string
8 | }
9 |
10 | export type ContainerGenericBetter = [
11 | container?: T,
12 | error?: Error,
13 | key?: string,
14 | ]
15 |
16 | export function useBetterGenericContainer(
17 | containerPromise: () => Promise,
18 | subscribeFunction: (cb: (err: any, container: T) => void) => () => void,
19 | containerKey: string,
20 | ): ContainerGenericBetter {
21 | const [data, setData] = useState(undefined)
22 | const [error, setError] = useState(undefined)
23 |
24 | // Update container
25 | useEffect(() => {
26 | return subscribeFunction((err, cont) => {
27 | if (err) {
28 | setError(err)
29 | setData(null)
30 | }
31 | setData(cont)
32 | })
33 | }, [subscribeFunction])
34 |
35 | // We can add optimizations later.
36 | useEffect(() => {
37 | try {
38 | // Apparently it will not always be a promise???
39 | // Not sure what I meant to code :/
40 | const providedValue = containerPromise()
41 | if (providedValue instanceof Promise) {
42 | providedValue
43 | .then((container) => {
44 | setData(container)
45 | })
46 | .catch((e) => {
47 | setError(e)
48 | })
49 | } else {
50 | setData(providedValue)
51 | }
52 | } catch (e) {
53 | setError(e)
54 | }
55 | }, [])
56 |
57 | return [data, error, containerKey]
58 | }
59 |
--------------------------------------------------------------------------------
/iti-react/tests/_utils.ts:
--------------------------------------------------------------------------------
1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w))
2 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/_mock-app-components.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useMemo, ReactNode } from "react"
2 | import { generateEnsureContainerSet } from "../../src/index"
3 | import { useMockAppContainerSet, MyRootCont } from "./_mock-app-hooks"
4 | import { getMainMockAppContainer } from "./_mock-app-container"
5 |
6 | const x = generateEnsureContainerSet(() =>
7 | useMockAppContainerSet(["aCont", "bCont"]),
8 | )
9 | export const EnsureNewKitchenConainer = x.EnsureWrapper
10 | export const useNewKitchenContext = x.contextHook
11 |
12 | export function MockAppWrapper({ children }: { children: ReactNode }) {
13 | const store = useMemo(() => getMainMockAppContainer(), [])
14 |
15 | return {children}
16 | }
17 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/_mock-app-container.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 |
3 | import { provideAContainer } from "./container.a"
4 | import { provideBContainer } from "./container.b"
5 | import { provideCContainer } from "./container.c"
6 |
7 | export type MockAppNode = ReturnType
8 | export function getMainMockAppContainer() {
9 | let node = createContainer()
10 | let k = node
11 | .upsert({ aCont: async () => provideAContainer() })
12 | .upsert((c) => {
13 | return {
14 | bCont: async () => provideBContainer(await c.aCont),
15 | }
16 | })
17 | .upsert((c, node) => {
18 | return {
19 | cCont: async () =>
20 | provideCContainer(await c.aCont, await node.get("bCont"), k),
21 | }
22 | })
23 | return k
24 | }
25 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/_mock-app-hooks.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { getContainerSetHooks } from "../../src/react/library.hook-generator"
3 | import { MockAppNode } from "./_mock-app-container"
4 |
5 | export const MyRootCont = React.createContext({})
6 |
7 | let mega = getContainerSetHooks(MyRootCont)
8 | export const useMockAppContainerSet = mega.useContainerSet
9 | export const useMockAppContainer = mega.useContainer
10 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/container.a.ts:
--------------------------------------------------------------------------------
1 | import { A1, A2, A3 } from "./store.a"
2 |
3 | export interface A_Container {
4 | a1: A1
5 | a2: A2
6 | a3: A3
7 | }
8 |
9 | export async function provideAContainer(): Promise {
10 | const a1 = new A1()
11 | const a2 = new A2(a1)
12 | const a3 = new A3(a1, a2)
13 |
14 | return {
15 | a1,
16 | a2,
17 | a3,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/container.b.ts:
--------------------------------------------------------------------------------
1 | import type { A_Container } from "./container.a"
2 | import { B1, B2 } from "./store.b"
3 |
4 | export interface B_Container {
5 | b1: B1
6 | b2: B2
7 | }
8 |
9 | export async function provideBContainer(a: A_Container): Promise {
10 | const b1 = new B1(a.a2)
11 |
12 | const b2 = new B2(a.a1)
13 |
14 | return {
15 | b1,
16 | b2,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/container.c.ts:
--------------------------------------------------------------------------------
1 | import type { A_Container } from "./container.a"
2 | import { B_Container } from "./container.b"
3 | import { C1, C2 } from "./store.c"
4 | import { MockAppNode } from "./_mock-app-container"
5 | // import { MockAppContainer } from "./_mock-app-container"
6 |
7 | export interface C_Container {
8 | c1: C1
9 | c2: C2
10 | upgradeCContainer: (x?: number) => void
11 | }
12 |
13 | export async function provideCContainer(
14 | a: A_Container,
15 | b: B_Container,
16 | container: MockAppNode,
17 | ): Promise {
18 | const c1 = new C1(a.a2)
19 | const c2 = new C2(a.a1, b.b2, 5)
20 |
21 | async function replacer(ovenSize = 10) {
22 | const c1 = new C1(a.a2)
23 | const c2 = new C2(a.a1, b.b2, ovenSize)
24 | container.upsert(() => ({
25 | cCont: async () => {
26 | return {
27 | c1,
28 | c2,
29 | upgradeCContainer: (ovenSize = 10) => replacer(ovenSize),
30 | }
31 | },
32 | }))
33 | }
34 |
35 | return {
36 | c1,
37 | c2,
38 | upgradeCContainer: replacer,
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/store.a.ts:
--------------------------------------------------------------------------------
1 | export class A1 {
2 | constructor() {}
3 | }
4 | export class A2 {
5 | constructor(private a1: A1) {}
6 | }
7 | export class A3 {
8 | constructor(private a1: A1, private a2: A2) {}
9 | }
10 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/store.b.ts:
--------------------------------------------------------------------------------
1 | import type { A1, A2 } from "./store.a"
2 |
3 | export class B1 {
4 | constructor(private a2: A2) {
5 | // 1. Subscribe to event emitter
6 | // 2. Pub update
7 | }
8 | }
9 | export class B2 {
10 | constructor(private a1: A1) {}
11 | }
12 |
--------------------------------------------------------------------------------
/iti-react/tests/mocks/store.c.ts:
--------------------------------------------------------------------------------
1 | import type { A1, A2 } from "./store.a"
2 | import { B2 } from "./store.b"
3 |
4 | export class C1 {
5 | constructor(private a2: A2) {
6 | // 1. Subscribe to event emitter
7 | // 2. Pub update
8 | }
9 | }
10 | export class C2 {
11 | constructor(private a1: A1, b2: B2, readonly size) {}
12 | }
13 |
--------------------------------------------------------------------------------
/iti-react/tests/react-hooks-check-types.tsd-only.ts:
--------------------------------------------------------------------------------
1 | import { expectType, expectNotType } from "tsd"
2 | import {
3 | useMockAppContainer,
4 | useMockAppContainerSet,
5 | } from "./mocks/_mock-app-hooks"
6 | import { A_Container } from "./mocks/container.a"
7 | import { B_Container } from "./mocks/container.b"
8 | import { C_Container } from "./mocks/container.c"
9 | import { MockAppWrapper } from "./mocks/_mock-app-components"
10 |
11 | // useMockAppContainer should not return `any` type"
12 | ;(() => {
13 | const containers = useMockAppContainer()
14 | expectNotType(containers)
15 | })()
16 |
17 | // useMockAppContainer should test if useMockAppContainer gets correct types
18 | ;(() => {
19 | const [aContainer] = useMockAppContainer().aCont
20 | expectType(aContainer)
21 |
22 | if (aContainer != null) {
23 | expectType(aContainer)
24 | }
25 | })()
26 |
27 | // useMockAppContainerSet should not return any
28 | ;(() => {
29 | const containerSet = useMockAppContainerSet(["aCont", "bCont"])
30 | expectNotType(containerSet)
31 | })()
32 |
33 | // useMockAppContainerSet should return exact types
34 | ;(() => {
35 | const [containerSet, containerSetErr] = useMockAppContainerSet([
36 | "aCont",
37 | "bCont",
38 | ])
39 |
40 | const [containerSet2, containerSetErr2] = useMockAppContainerSet((c) => [
41 | c.aCont,
42 | c.bCont,
43 | ])
44 |
45 | if (containerSet != null) {
46 | expectType(containerSet.aCont)
47 | // expectType(containerSet.cCont)
48 | }
49 |
50 | if (containerSet2 != null) {
51 | expectType(containerSet2.bCont)
52 | // expectType(containerSet.cCont)
53 | }
54 | })()
55 |
--------------------------------------------------------------------------------
/iti-react/tests/types-for-tests.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/iti-react/tests/z.container-hook.spec.tsx.bkp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | THIS file is on hold because I am not that smart to test these react hooks
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | import React from "react"
15 | import { renderHook, act } from "@testing-library/react-hooks/dom"
16 | // import { renderHook, } from '@testing-library/react-hooks"
17 | import { expectType, expectError } from "tsd"
18 |
19 | import {
20 | useMockAppContainer,
21 | useMockAppContainerSet,
22 | useMockAppContainerSetNew,
23 | } from "./mocks/_mock-app-hooks"
24 | import { A_Container } from "./mocks/container.a"
25 | import { B_Container } from "./mocks/container.b"
26 | import { C_Container } from "./mocks/container.c"
27 | import { MockAppWrapper } from "./mocks/_mock-app-components"
28 |
29 | // it("should pass basic ts test", () => {
30 | // let [authCont] = useContainer().auth
31 | // expectType(authCont)
32 | // })
33 |
34 | // const App = () => <>learn react>
35 | // // https://github.com/SamVerschueren/tsd
36 |
37 | // test("renders learn react link", () => {
38 | // render()
39 | // const linkElement = screen.getByText(/learn react/i)
40 | // expect(linkElement).toBeInTheDocument()
41 | // })
42 |
43 | /**
44 | * Testing react hooks is waste of time
45 | */
46 |
47 | it.only("should pass basic ts test", (cb) => {
48 | ;(async () => {
49 | let a = 12
50 | //
51 | // result.current.increment()
52 | const wrapper = (p) => {p.children}
53 | const { result } = renderHook(() => useMockAppContainer(), {
54 | wrapper,
55 | })
56 | console.log("rez", result)
57 | console.log("rez", result.all)
58 | console.log("rez", result.current)
59 |
60 | await act(async () => {
61 | return result.current.aCont
62 | })
63 | // const { result } = renderHook(() => useMockAppContainer().aCont)
64 |
65 | expect(a).toBe(12)
66 | cb()
67 | })()
68 |
69 | // let [aContainer] =
70 | // let contSet = useMockAppContainerSetNew((c) => [c.aCont, c.bCont, c.cCont])
71 |
72 | // // @ts-expect-error
73 | // expectType(aContainer) // because aContainer should be undefined on first render
74 |
75 | // expect(() => {
76 | // // @ts-expect-error
77 | // expectError(contSet.cCont)
78 | // }).toThrow()
79 |
80 | // if (aContainer == null) return null
81 | // if (contSet == null) return null
82 |
83 | // expectType(aContainer)
84 | // expectType(contSet.bCont)
85 |
86 | // return null
87 |
88 | // const App = () => {
89 | // let [aContainer] = useMockAppContainer().aCont
90 | // let contSet = useMockAppContainerSetNew((c) => [c.aCont, c.bCont, c.cCont])
91 |
92 | // // @ts-expect-error
93 | // expectType(aContainer) // because aContainer should be undefined on first render
94 |
95 | // expect(() => {
96 | // // @ts-expect-error
97 | // expectError(contSet.cCont)
98 | // }).toThrow()
99 |
100 | // if (aContainer == null) return null
101 | // if (contSet == null) return null
102 |
103 | // expectType(aContainer)
104 | // expectType(contSet.bCont)
105 |
106 | // return null
107 | // }
108 |
109 | // render(
110 | //
111 | //
112 | // ,
113 | // )
114 | })
115 |
--------------------------------------------------------------------------------
/iti-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "outDir": "dist/",
5 | "declarationDir": "dist/",
6 | "lib": ["dom", "dom.iterable", "esnext"],
7 | "types": ["@types/jest"],
8 | "noImplicitAny": false,
9 | "experimentalDecorators": true,
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "declaration": true,
22 | "jsx": "react"
23 | },
24 | "include": ["src/**/*"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/iti-react/tsd_project/dummy.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/iti-react/tsd_project/dummy.d.ts
--------------------------------------------------------------------------------
/iti-react/tsd_project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": "dummy.d.ts",
3 | "tsd": {
4 | "directory": "../tests"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iti/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | coverage
4 | # stryker temp files
5 | .stryker-tmp
6 | .env
7 | reports
--------------------------------------------------------------------------------
/iti/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Nick Olszanski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/iti/Makefile:
--------------------------------------------------------------------------------
1 | # Sane defaults
2 | SHELL := /bin/bash
3 | .ONESHELL:
4 | .SHELLFLAGS := -eu -o pipefail -c
5 | .DELETE_ON_ERROR:
6 | MAKEFLAGS += --warn-undefined-variables
7 | MAKEFLAGS += --no-builtin-rules
8 | # Default params
9 | # environment ?= "dev"
10 |
11 | # ---------------------- COMMANDS ---------------------------
12 |
13 |
14 | bin = ./node_modules/.bin
15 |
16 | build: ## Build the project
17 | @ rm -rf ./dist
18 | $(bin)/microbundle build -o dist/ --format cjs,modern
19 | find ./dist -name "*.d.ts" -exec sh -c 'cp "$$1" "$${1%%.d.ts}.d.mts"' sh {} \;
20 | find ./dist -name "*.d.ts" -exec sh -c 'cp "$$1" "$${1%%.d.ts}.d.cts"' sh {} \;
21 | mv ./dist/iti.cjs ./dist/index.cjs
22 | mv ./dist/iti.cjs.map ./dist/index.cjs.map
23 | mv ./dist/iti.mjs ./dist/index.mjs
24 | mv ./dist/iti.mjs.map ./dist/index.mjs.map
25 | sed -i.bak "s/iti.mjs.map/index.mjs.map/g" ./dist/index.mjs && rm -f ./dist/index.mjs.bak
26 | sed -i.bak "s/iti.cjs.map/index.cjs.map/g" ./dist/index.cjs && rm -f ./dist/index.cjs.bak
27 | ls -l ./dist
28 |
29 | # https://www.npmjs.com/package/@arethetypeswrong/cli
30 | check: ## Check the project
31 | make build
32 | npm pack
33 | attw ./iti-0.7.0-alpha.4.tgz
34 | # find ./dist -name "*.d.ts" -delete;
35 |
36 |
37 | # -----------------------------------------------------------
38 | # CAUTION: If you have a file with the same name as make
39 | # command, you need to add it to .PHONY below, otherwise it
40 | # won't work. E.g. `make run` wouldn't work if you have
41 | # `run` file in pwd.
42 | .PHONY: help ssl
43 |
44 | # -----------------------------------------------------------
45 | # ----- (Makefile helpers and decoration) --------
46 | # -----------------------------------------------------------
47 |
48 | .DEFAULT_GOAL := help
49 | # check https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
50 | NC = \033[0m
51 | ERR = \033[31;1m
52 | TAB := '%-20s' # Increase if you have long commands
53 |
54 | # tput colors
55 | red := $(shell tput setaf 1)
56 | green := $(shell tput setaf 2)
57 | yellow := $(shell tput setaf 3)
58 | blue := $(shell tput setaf 4)
59 | cyan := $(shell tput setaf 6)
60 | cyan80 := $(shell tput setaf 86)
61 | grey500 := $(shell tput setaf 244)
62 | grey300 := $(shell tput setaf 240)
63 | bold := $(shell tput bold)
64 | underline := $(shell tput smul)
65 | reset := $(shell tput sgr0)
66 |
67 | help:
68 | @printf '\n'
69 | @printf ' $(underline)$(grey500)Available make commands:$(reset)\n\n'
70 | @# Print non-check commands with comments
71 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:.+#.+$$' $(MAKEFILE_LIST) \
72 | | grep -v '^check-' \
73 | | grep -v '^env-' \
74 | | grep -v '^arg-' \
75 | | sed 's/:.*#/: #/g' \
76 | | awk 'BEGIN {FS = "[: ]+#[ ]+"}; \
77 | {printf " $(grey300) make $(reset)$(cyan80)$(bold)$(TAB) $(reset)$(grey300)# %s$(reset)\n", \
78 | $$1, $$2}'
79 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:( +\w+-\w+)*$$' $(MAKEFILE_LIST) \
80 | | grep -v help \
81 | | awk 'BEGIN {FS = ":"}; \
82 | {printf " $(grey300) make $(reset)$(cyan80)$(bold)$(TAB)$(reset)\n", \
83 | $$1}'
84 | @echo -e "\n $(underline)$(grey500)Helper/Checks$(reset)\n"
85 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:.+#.+$$' $(MAKEFILE_LIST) \
86 | | grep -E '^(check|arg|env)-' \
87 | | awk 'BEGIN {FS = "[: ]+#[ ]+"}; \
88 | {printf " $(grey300) make $(reset)$(grey500)$(TAB) $(reset)$(grey300)# %s$(reset)\n", \
89 | $$1, $$2}'
90 | @echo -e ""
91 |
--------------------------------------------------------------------------------
/iti/notes-and-ideas.md:
--------------------------------------------------------------------------------
1 | ### lookup table
2 |
3 | For runtime optimizations of the search in async nodes we can provide tokens as a second argument in an `addPromise `
4 |
5 | ```ts
6 | let n = createContainer()
7 | .addNode({ a: 1, b: 2 })
8 | .addPromise(
9 | async (c) => {
10 | return { c: 3, d: 4 }
11 | },
12 | ["c", "d"], // <-- this might be a purely runtime optimization to index the lookup and make code even more lazy
13 | )
14 | .addPromiseStrict(
15 | // Or even better add a new method
16 | async (c) => {
17 | return { c: 3, d: 4 }
18 | },
19 | ["c", "d"], // that forces you to list all deps keys and TS can actually check it!!!
20 | )
21 | ```
22 |
23 | ### Nano emitter
24 |
25 | try nano emitter from evil martians but check if they support multiple subscribes gracefully via `node lol.js`
26 |
27 | ### Disable promise for `addNode`
28 |
29 | add node should TS throw if pass async. TYpescirpt should lookup return type and dissallow promise return type
30 |
31 | ### Disable overrides for `addNode`
32 |
33 | we can TS throw if we see that user has provided a duplicate token.
34 | Dublicate tokens maybe then could be added via `overrideNode`
35 |
36 | ### Add options for `addNode`
37 |
38 | - first option is a lookup table of tokens
39 | - second idea would be a force override parameter when you want to force flush changes
40 |
41 | # notes
42 |
43 | For container set there where a couple of options.
44 | I coded option 1 because it was easy.
45 |
46 | But now I want option
47 |
48 | ```js
49 | // Option 1
50 | let containerSet1 = await cont.getContainerSet(["aCont", "bCont", "cCont"])
51 |
52 | // Option 2
53 | let containerSet2 = await cont.getContainerSet((c) => [
54 | c.aCont,
55 | c.bCont,
56 | c.Cont,
57 | ])
58 |
59 | // Option 3
60 | let containerSet2 = await cont.getContainerSet(({ aCont, bCont, cCont }) => ({
61 | aCont,
62 | bCont,
63 | cCont,
64 | }))
65 |
66 | // Option 4
67 | let c = cont.tokens
68 | let containerSet2 = await cont.getContainerSet([c.aCont, c.bCont, c.Cont])
69 | ```
70 |
71 | ```js
72 | // fromEntries :: [[a, b]] -> {a: b}
73 | // Does the reverse of Object.entries.
74 | const fromEntries = (list) => {
75 | const result = {}
76 |
77 | for (let [key, value] of list) {
78 | result[key] = value
79 | }
80 |
81 | return result
82 | }
83 |
84 | // addAsset :: (k, Promise a) -> Promise (k, a)
85 | const addAsset = ([name, assetPromise]) =>
86 | assetPromise.then((asset) => [name, asset])
87 |
88 | // loadAll :: {k: Promise a} -> Promise {k: a}
89 | const loadAll = (assets) =>
90 | Promise.all(Object.entries(assets).map(addAsset)).then(fromEntries)
91 | ```
92 |
--------------------------------------------------------------------------------
/iti/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti",
3 | "version": "0.7.0",
4 | "description": "~1kB Dependency Injection Library for Typescript and React with a unique async flow support",
5 | "sideEffects": false,
6 | "type": "module",
7 | "main": "./dist/index.cjs",
8 | "module": "./dist/index.mjs",
9 | "typings": "./dist/index.d.mts",
10 | "types": "./dist/index.d.mts",
11 | "exports": {
12 | ".": {
13 | "import": {
14 | "types": "./dist/index.d.mts",
15 | "default": "./dist/index.mjs"
16 | },
17 | "require": {
18 | "types": "./dist/index.d.cts",
19 | "default": "./dist/index.cjs"
20 | },
21 | "default": "./dist/index.mjs"
22 | },
23 | "./package.json": "./package.json"
24 | },
25 | "source": "src/index.ts",
26 | "publishConfig": {
27 | "source": "./src/index.ts",
28 | "main": "./dist/index.cjs",
29 | "module": "./dist/index.mjs"
30 | },
31 | "files": [
32 | "dist"
33 | ],
34 | "scripts": {
35 | "build": "make build",
36 | "stryker": "stryker",
37 | "stryker:run": "stryker run",
38 | "test": "vitest && yarn tsd",
39 | "tsd": "tsd tsd_project",
40 | "v": "vitest",
41 | "vitest:types": "vitest --typecheck"
42 | },
43 | "dependencies": {
44 | "utility-types": "^3.10.0"
45 | },
46 | "devDependencies": {
47 | "@stryker-mutator/core": "^8.6.0",
48 | "@stryker-mutator/jest-runner": "^8.6.0",
49 | "@stryker-mutator/typescript-checker": "^8.6.0",
50 | "@stryker-mutator/vitest-runner": "^8.6.0",
51 | "@types/jest": "^29.5.14",
52 | "@types/react": "^18.0.15",
53 | "jest": "^29.7.0",
54 | "microbundle": "^0.15.1",
55 | "nodemon": "^2.0.19",
56 | "stryker-cli": "^1.0.2",
57 | "ts-jest": "^29.2.5",
58 | "tsd": "^0.22.0",
59 | "typescript": "^4.7.4",
60 | "vitest": "^2.1.4"
61 | },
62 | "engines": {
63 | "node": ">=12"
64 | },
65 | "license": "MIT",
66 | "authors": [
67 | "Nick Olszanski "
68 | ],
69 | "repository": "molszanski/iti",
70 | "keywords": [
71 | "ioc",
72 | "di",
73 | "inversion of control",
74 | "dependency injection",
75 | "dependency inversion",
76 | "inversion of control container",
77 | "container",
78 | "javascript",
79 | "typescript",
80 | "type-safe"
81 | ],
82 | "homepage": "https://itijs.org",
83 | "// CONFIGS: ": "Package configs",
84 | "prettier": {
85 | "semi": false,
86 | "singleQuote": false,
87 | "arrowParens": "always",
88 | "trailingComma": "all"
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/iti/src/_utils.ts:
--------------------------------------------------------------------------------
1 | export type UnPromisify = T extends Promise ? U : T
2 |
3 | export type GetContainerFormat any> =
4 | UnPromisify>
5 |
6 | export function addGetter(object, key, fn: any) {
7 | Object.defineProperty(object, key, {
8 | get() {
9 | return fn()
10 | },
11 | enumerable: true,
12 | })
13 | }
14 |
15 | export type Assign = {
16 | [Token in keyof ({
17 | [K in keyof OldContext]: OldContext[K]
18 | } & {
19 | [K in keyof NewContext]: NewContext[K]
20 | })]: Token extends keyof NewContext
21 | ? NewContext[Token]
22 | : Token extends keyof OldContext
23 | ? OldContext[Token]
24 | : never
25 | }
26 |
27 | export type Prettify = T extends infer U ? { [K in keyof U]: U[K] } : never
28 |
29 | export type UnpackFunction = T extends (...args: any) => infer U ? U : T
30 | export type UnpackObject = {
31 | [K in keyof T]: UnpackFunction
32 | }
33 |
34 | export type UnpromisifyObject = {
35 | [K in keyof T]: UnPromisify
36 | }
37 | // keep
38 | // type AssignAndUnpackObjects = UnpromisifyObject<
39 | // UnpackObject>
40 | // >
41 |
42 | export type FullyUnpackObject = UnpromisifyObject>
43 |
44 | export type KeysOrCb =
45 | | Array
46 | | ((t: { [K in keyof Context]: K }) => Array)
47 |
48 | export type MyRecord = {
49 | [K in keyof O]: T
50 | }
51 | export type ContextGetter = {
52 | [CK in keyof Context]: UnpackFunction
53 | }
54 |
55 | export type UnpackFunctionReturn = T extends (arg: T) => infer U ? U : T
56 | export type ContextGetterWithCache = {
57 | [CK in keyof Context]: UnpackFunctionReturn
58 | }
59 |
60 | export function _intersectionKeys(
61 | needle: { [key: string]: any },
62 | haystack: { [key: string]: any },
63 | ) {
64 | let haystackKeys = Object.keys(haystack)
65 | let duplicates = haystackKeys.filter((x) => x in needle)
66 | if (duplicates.length === 0) {
67 | return undefined
68 | }
69 | return duplicates.join("', '")
70 | }
71 |
--------------------------------------------------------------------------------
/iti/src/errors.ts:
--------------------------------------------------------------------------------
1 | export class ItiError extends Error {}
2 | export class ItiResolveError extends ItiError {}
3 | export class ItiTokenError extends ItiError {}
4 |
--------------------------------------------------------------------------------
/iti/src/index.ts:
--------------------------------------------------------------------------------
1 | // Main lib
2 | export {
3 | createContainer,
4 | createContainer as makeRoot,
5 | Container,
6 | } from "./iti.js"
7 |
8 | // Helper types
9 | export type {
10 | GetContainerFormat,
11 | UnPromisify,
12 | UnpackFunction,
13 | Prettify,
14 | } from "./_utils.js"
15 | export type { Emitter } from "./nanoevents.js"
16 |
--------------------------------------------------------------------------------
/iti/src/nanoevents.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * From
3 | * https://github.com/ai/nanoevents
4 | *
5 | * Sadly, can't install it via npm. Some build issue
6 | */
7 |
8 | //////////////////
9 |
10 | interface EventsMap {
11 | [event: string]: any
12 | }
13 |
14 | interface DefaultEvents extends EventsMap {
15 | [event: string]: (...args: any) => void
16 | }
17 |
18 | export interface Unsubscribe {
19 | (): void
20 | }
21 |
22 | export declare class Emitter {
23 | /**
24 | * Event names in keys and arrays with listeners in values.
25 | *
26 | * ```js
27 | * emitter1.events = emitter2.events
28 | * emitter2.events = { }
29 | * ```
30 | */
31 | events: Partial<{ [E in keyof Events]: Events[E][] }>
32 |
33 | /**
34 | * Add a listener for a given event.
35 | *
36 | * ```js
37 | * const unbind = ee.on('tick', (tickType, tickDuration) => {
38 | * count += 1
39 | * })
40 | *
41 | * disable () {
42 | * unbind()
43 | * }
44 | * ```
45 | *
46 | * @param event The event name.
47 | * @param cb The listener function.
48 | * @returns Unbind listener from event.
49 | */
50 | on(this: this, event: K, cb: Events[K]): Unsubscribe
51 |
52 | /**
53 | * Calls each of the listeners registered for a given event.
54 | *
55 | * ```js
56 | * ee.emit('tick', tickType, tickDuration)
57 | * ```
58 | *
59 | * @param event The event name.
60 | * @param args The arguments for listeners.
61 | */
62 | emit(
63 | this: this,
64 | event: K,
65 | ...args: Parameters
66 | ): void
67 | }
68 |
69 | /**
70 | * Create event emitter.
71 | *
72 | * ```js
73 | * import { createNanoEvents } from 'nanoevents'
74 | *
75 | * class Ticker {
76 | * constructor() {
77 | * this.emitter = createNanoEvents()
78 | * }
79 | * on(...args) {
80 | * return this.emitter.on(...args)
81 | * }
82 | * tick() {
83 | * this.emitter.emit('tick')
84 | * }
85 | * }
86 | * ```
87 | */
88 |
89 | export function createNanoEvents<
90 | Events extends EventsMap = DefaultEvents,
91 | >(): Emitter {
92 | return {
93 | events: {},
94 | emit(event, ...args) {
95 | // @ts-ignore
96 | ;(this.events[event] || []).forEach((i) => i(...args))
97 | },
98 | on(event, cb) {
99 | // @ts-ignore
100 | ;(this.events[event] = this.events[event] || []).push(cb)
101 | return () =>
102 | // @ts-ignore
103 | (this.events[event] = (this.events[event] || []).filter(
104 | (i) => i !== cb,
105 | ))
106 | },
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/iti/stryker.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/api/schema/stryker-core.json",
3 | "_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information",
4 | "packageManager": "yarn",
5 | "_commandRunner": { "command": "yarn vitest" },
6 | "reporters": ["html", "clear-text", "progress", "dashboard", "json"],
7 | "testRunner": "vitest",
8 | "disableTypeChecks": "{test,tests,src,lib}/**/*.{js,ts,jsx,tsx,html,vue}",
9 | "coverageAnalysis": "off",
10 | "checkers": ["typescript"],
11 | "tsconfigFile": "tsconfig.json"
12 | }
13 |
--------------------------------------------------------------------------------
/iti/tests/__snapshots__/container-getter.vi.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Getter tests > should get a single container 1`] = `
4 | {
5 | "b1": B1 {
6 | "a2": A2 {
7 | "a1": A1 {},
8 | },
9 | },
10 | "b2": B2 {
11 | "a1": A1 {},
12 | },
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/iti/tests/__snapshots__/container-set.vi.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Container set: > should get container set via a new API 1`] = `
4 | {
5 | "aCont": {
6 | "a1": {},
7 | "a2": {
8 | "a1": {},
9 | },
10 | "a3": {
11 | "a1": {},
12 | "a2": {
13 | "a1": {},
14 | },
15 | },
16 | },
17 | "bCont": {
18 | "b1": {
19 | "a2": {
20 | "a1": {},
21 | },
22 | },
23 | "b2": {
24 | "a1": {},
25 | },
26 | },
27 | }
28 | `;
29 |
30 | exports[`Container set: > should get two containers that are async 1`] = `
31 | {
32 | "aCont": {
33 | "a1": {},
34 | "a2": {
35 | "a1": {},
36 | },
37 | "a3": {
38 | "a1": {},
39 | "a2": {
40 | "a1": {},
41 | },
42 | },
43 | },
44 | "bCont": {
45 | "b1": {
46 | "a2": {
47 | "a1": {},
48 | },
49 | },
50 | "b2": {
51 | "a1": {},
52 | },
53 | },
54 | }
55 | `;
56 |
--------------------------------------------------------------------------------
/iti/tests/__snapshots__/getter.vi.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Node getter > should get nested containers 1`] = `
4 | {
5 | "b1": B1 {
6 | "a2": A2 {
7 | "a1": A1 {},
8 | },
9 | },
10 | "b2": B2 {
11 | "a1": A1 {},
12 | },
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/iti/tests/_utils.ts:
--------------------------------------------------------------------------------
1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w))
2 |
--------------------------------------------------------------------------------
/iti/tests/container-get-values.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, beforeEach, vi } from "vitest"
2 | import { createContainer } from "../src/iti"
3 |
4 | describe("Node.get()", () => {
5 | let c0 = createContainer()
6 | beforeEach(() => (c0 = createContainer()))
7 |
8 | it("should return a value as a value", async () => {
9 | const c1 = c0.add({ a: 123 })
10 | expect(c1.get("a")).toBe(123)
11 | })
12 |
13 | it("should throw if a token is missing", async () => {
14 | const c1 = c0.add({ a: 123 })
15 | expect(() => {
16 | // @ts-expect-error
17 | c1.get("c")
18 | }).toThrowError()
19 | })
20 | it("should return function result and not a function", async () => {
21 | const c1 = c0.add({
22 | functionTOken: () => "optimus",
23 | })
24 | expect(c1.get("functionTOken")).toBe("optimus")
25 | })
26 |
27 | it("should return correct tokens for merged and overridden nodes", () => {
28 | const c = c0.add({ optimus: () => "prime", a: 123 }).upsert({ a: "123" })
29 | expect(c.getTokens()).toMatchObject({
30 | optimus: "optimus",
31 | a: "a",
32 | })
33 | })
34 |
35 | it("should return cached value of a function", async () => {
36 | let fn = vi.fn()
37 | const c1 = c0.add({
38 | optimus: () => {
39 | fn()
40 | return "prime"
41 | },
42 | })
43 | c1.get("optimus")
44 | c1.get("optimus")
45 | c1.get("optimus")
46 | expect(c1.get("optimus")).toBe("prime")
47 | expect(fn).toHaveBeenCalledTimes(1)
48 | })
49 |
50 | it("should return promises of async functions", async () => {
51 | const c1 = c0.add({
52 | optimus: async () => "prime",
53 | })
54 | expect(await c1.get("optimus")).toBe("prime")
55 | })
56 |
57 | it("should handle async errors with a simple try/catch", async () => {
58 | const node = c0
59 | .add({
60 | optimus: async () => "prime",
61 | megatron: async () => {
62 | throw "all hail megatron"
63 | },
64 | })
65 | .add((ctx) => ({
66 | decepticons: async () => {
67 | leader: await ctx.megatron
68 | },
69 | }))
70 |
71 | expect(await node.get("optimus")).toBe("prime")
72 | try {
73 | await node.get("megatron")
74 | } catch (e) {
75 | expect(e).toBe("all hail megatron")
76 | }
77 |
78 | try {
79 | await node.items.decepticons
80 | } catch (e) {
81 | expect(e).toBe("all hail megatron")
82 | }
83 | })
84 |
85 | it("should handle async errors with for getContainerSet", async () => {
86 | const node = c0
87 | .add({
88 | optimus: async () => "prime",
89 | megatron: async () => {
90 | throw "all hail megatron"
91 | },
92 | })
93 | .add((ctx) => ({
94 | decepticons: async () => {
95 | leader: await ctx.megatron
96 | },
97 | }))
98 |
99 | try {
100 | await node.getContainerSet(["optimus", "decepticons"])
101 | } catch (e) {
102 | expect(e).toBe("all hail megatron")
103 | }
104 | })
105 |
106 | it("should call container provider once, but container token twice", () => {
107 | const fn1 = vi.fn()
108 | const fn2 = vi.fn()
109 |
110 | const node = c0.add({
111 | autobots: () => {
112 | fn1()
113 | return {
114 | optimus: () => {
115 | fn2()
116 | return "autobots assemble"
117 | },
118 | bumblebee: "bumblebee",
119 | jazz: "jazz",
120 | }
121 | },
122 | })
123 |
124 | expect(fn1).not.toBeCalled()
125 | expect(fn2).not.toBeCalled()
126 |
127 | let a1 = node.get("autobots")
128 | a1.optimus()
129 | let a2 = node.get("autobots")
130 | a2.optimus()
131 | expect(fn1).toHaveBeenCalledTimes(1)
132 | expect(fn2).toHaveBeenCalledTimes(2)
133 | })
134 | })
135 |
--------------------------------------------------------------------------------
/iti/tests/container-getter.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, beforeEach, vi } from "vitest"
2 | import { createContainer } from "../src/iti"
3 | import { getMainMockAppContainer } from "./mocks/_mock-app-container"
4 | import { wait } from "./_utils"
5 |
6 | describe("Getter tests", () => {
7 | let c0 = createContainer()
8 | beforeEach(() => (c0 = createContainer()))
9 |
10 | it("should get a single container", async () => {
11 | const cont = getMainMockAppContainer()
12 | expect(cont.items).toHaveProperty("bCont")
13 | expect(cont.items.aCont).toBeInstanceOf(Promise)
14 |
15 | let b = await cont.items.bCont
16 | expect(b).toHaveProperty("b2")
17 | expect(b).toMatchSnapshot()
18 | })
19 |
20 | it("should subscribe to a single container", async () => {
21 | const cont = getMainMockAppContainer()
22 | expect(cont.items).toHaveProperty("bCont")
23 | expect(cont.items.aCont).toBeInstanceOf(Promise)
24 |
25 | let m = vi.fn()
26 | cont.subscribeToContainer("cCont", m)
27 | let cCont = await cont.get("cCont")
28 | cCont.upgradeCContainer()
29 |
30 | let c = await cont.get("cCont")
31 | expect(m).toHaveBeenCalled()
32 | expect(c.c2.size).toBe(10)
33 | await wait(20)
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/iti/tests/container-set.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi } from "vitest"
2 | import { getMainMockAppContainer } from "./mocks/_mock-app-container"
3 | import { wait } from "./_utils"
4 |
5 | describe("Container set:", () => {
6 | it("should get two containers that are async", async () => {
7 | const cont = getMainMockAppContainer()
8 | let containerSet = await cont.getContainerSet(["aCont", "bCont"])
9 |
10 | expect(containerSet).toHaveProperty("aCont")
11 | expect(containerSet).toHaveProperty("bCont")
12 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} })
13 |
14 | expect(containerSet).toMatchSnapshot(containerSet)
15 | })
16 |
17 | it("should subscribe to container set change", async () => {
18 | const cont = getMainMockAppContainer()
19 | let containerSet = await cont.getContainerSet(["aCont", "bCont", "cCont"])
20 |
21 | expect(containerSet).toHaveProperty("aCont")
22 | expect(containerSet).toHaveProperty("bCont")
23 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} })
24 | expect(containerSet.cCont.c2.size).toBe(5)
25 |
26 | containerSet.cCont.upgradeCContainer()
27 | cont.subscribeToContainerSet(
28 | ["aCont", "bCont", "cCont"],
29 | (err, containerSet) => {
30 | expect(containerSet.cCont.c2.size).toBe(10)
31 | },
32 | )
33 | await cont.get("cCont")
34 | await wait(10)
35 | })
36 |
37 | it("should get container set via a new API", async () => {
38 | const cont = getMainMockAppContainer()
39 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.bCont])
40 |
41 | expect(containerSet).toHaveProperty("aCont")
42 | expect(containerSet).toHaveProperty("bCont")
43 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} })
44 | expect(containerSet).toMatchSnapshot(containerSet)
45 | })
46 |
47 | it("should subscribe to container set change via a new APi", async () => {
48 | const cont = getMainMockAppContainer()
49 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.cCont])
50 | expect(containerSet).toHaveProperty("aCont")
51 |
52 | const a = vi.fn()
53 | cont.subscribeToContainerSet(
54 | (c) => {
55 | return [c.aCont, c.cCont]
56 | },
57 | (err, containerSet) => {
58 | expect(containerSet.cCont.c2.size).toBe(10)
59 | a()
60 | },
61 | )
62 | containerSet.cCont.upgradeCContainer()
63 | await wait(10)
64 | expect(a).toHaveBeenCalledTimes(2)
65 | })
66 |
67 | it("should subscribe to container set change via a old APi", async () => {
68 | const cont = getMainMockAppContainer()
69 | let containerSet = await cont.getContainerSet(["aCont", "cCont"])
70 | expect(containerSet).toHaveProperty("aCont")
71 |
72 | cont.subscribeToContainerSet(
73 | (c) => {
74 | return [c.aCont, c.cCont]
75 | },
76 | (err, containerSet) => {
77 | expect(containerSet.cCont.c2.size).toBe(10)
78 | },
79 | )
80 |
81 | containerSet.cCont.upgradeCContainer()
82 | await wait(10)
83 | })
84 |
85 | it("should be able to unsubscribe from container set change", async () => {
86 | const cont = getMainMockAppContainer()
87 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.cCont])
88 |
89 | const fn = vi.fn()
90 | const unsub = cont.subscribeToContainerSet(
91 | (c) => [c.cCont],
92 | () => {
93 | fn()
94 | unsub()
95 | },
96 | )
97 | containerSet.cCont.upgradeCContainer()
98 | await wait(10)
99 | containerSet.cCont.upgradeCContainer()
100 | await wait(10)
101 | // Here we have two calls. And this should probably be double checked
102 | expect(fn).toHaveBeenCalledTimes(2)
103 | })
104 | })
105 |
--------------------------------------------------------------------------------
/iti/tests/dispose-graph.ts.api.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach } from "vitest"
2 |
3 | import { createContainer } from "../src"
4 | import { wait } from "./_utils"
5 | import { A, X, B, C, D, L, K, E, M, F } from "./mock-graph"
6 |
7 | /*
8 | ┌─────┐
9 | │ A │
10 | └─────┘
11 | ┌────────┴────────┐
12 | ▼ ▼
13 | ┌─────┐ ┌─────┐
14 | │ B │ │ C │────────────┐
15 | └─────┘ └─────┘ │
16 | └────────┬────────┘ │
17 | ▼ ▼
18 | ┌─────┐ ┌─────┐ ┌─────┐
19 | │ X │─────▶│ D │─────────────────▶│ E │
20 | └─────┘ └─────┘ └─────┘
21 | ┌─────┴────┐ ┌────┴────┐
22 | ▼ ▼ ▼ ▼
23 | ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
24 | │ L │ │ K │ │ M │ │ F │
25 | └─────┘ └─────┘ └─────┘ └─────┘
26 | */
27 | function getGraph() {
28 | return createContainer()
29 | .add({ a: () => new A(), x: () => new X() })
30 | .add((ctx) => ({
31 | b: () => new B(ctx.a),
32 | c: () => new C(ctx.a),
33 | }))
34 | .add((ctx) => ({
35 | d: () => new D(ctx.b, ctx.c, ctx.x),
36 | m: async () => 123,
37 | }))
38 | }
39 | /**
40 | * warning, partial graph disposal should not be implemented
41 | *
42 | * Due to teh async nature of the problem, the only way to implement it is to
43 | * track dependencies and dispose them in the reverse order of creation. Visitor pattern.
44 | * But since there might multiple async resolution requests, we might accidentally "track"
45 | * an unrelated dependency as a "visited" one.
46 | *
47 | * Hence when disposing, we would dispose unrelated dependencies as well.
48 | *
49 | * I don't think it would be really that useful anyway.
50 | *
51 | */
52 | describe("Disposing graph: [warning, this should never be implemented]", () => {
53 | let root = getGraph()
54 | beforeEach(() => {
55 | root = getGraph()
56 | })
57 |
58 | it("should call graph", async () => {
59 | const disposeLog: string[] = []
60 | const dis = (token: string) => disposeLog.push(token)
61 |
62 | const dDisposer = vi.fn()
63 | const node = root.addDisposer((ctx, node) => ({
64 | a: () => dis("a"),
65 | b: () => dis("b"),
66 | c: () => dis("c"),
67 | d: () => {
68 | dDisposer()
69 | dis("d")
70 | },
71 | x: (x) => {
72 | return dis("x")
73 | },
74 | }))
75 |
76 | const d = node.get("d")
77 | node.dispose("d")
78 |
79 | expect(d).toBeInstanceOf(D)
80 | await wait(10)
81 | expect(dDisposer).toHaveBeenCalledTimes(1)
82 |
83 | // for the future disposer graph
84 | // expect(disposeLog).toEqual(["d"])
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/iti/tests/exotic.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach } from "vitest"
2 | import { createContainer } from "../src/iti"
3 |
4 | describe("Perf and exotic tests:", () => {
5 | let root = createContainer()
6 |
7 | beforeEach(() => {
8 | root = createContainer()
9 | })
10 |
11 | describe("Node get:", () => {
12 | it("should not run into an infinite loop with recursive search", async () => {
13 | let r = root
14 | .add((c) => ({ a: async () => "A", b: "B", c: "C" }))
15 | .add((c, node) => ({
16 | d: async () => {
17 | expect(await node.items.b).toBe("B")
18 | return "D"
19 | },
20 | }))
21 | .upsert((c) => ({
22 | d: async () => {
23 | expect(await c.b).toBe("B")
24 | return "D2"
25 | },
26 | }))
27 |
28 | expect(await r.items.d).toBe("D2")
29 | }, 100)
30 |
31 | it("should never evaluate unrequested tokens, but pass correct reference to child node ", async () => {
32 | let r = root
33 | .add((c) => ({
34 | b: async () => {
35 | throw new Error()
36 | return { x: "x", y: "y" }
37 | },
38 | c: "C",
39 | }))
40 | .add((c) => {
41 | return {
42 | d: async () => {
43 | expect(c.c).toBe("C")
44 | return "D"
45 | },
46 | }
47 | })
48 | r.get("d")
49 | expect(await r.items.d).toBe("D")
50 | }, 100)
51 |
52 | // getTokens must be async welp
53 | it("should never evaluate unrequested tokens, but pass correct reference to child node \
54 | without a manual seal", async () => {
55 | let r = root
56 | .add((c) => ({
57 | b: async () => {
58 | throw new Error()
59 | return { x: "x", y: "y" }
60 | },
61 | c: "C",
62 | }))
63 | .add((c, node) => {
64 | return {
65 | d: async () => {
66 | expect(await node.items.c).toBe("C")
67 | return "D"
68 | },
69 | }
70 | })
71 |
72 | expect(await r.items.d).toBe("D")
73 | }, 100)
74 | })
75 | })
76 |
--------------------------------------------------------------------------------
/iti/tests/mock-graph.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ┌─────┐
3 | │ A │
4 | └─────┘
5 | ┌────────┴────────┐
6 | ▼ ▼
7 | ┌─────┐ ┌─────┐
8 | │ B │ │ C │────────────┐
9 | └─────┘ └─────┘ │
10 | └────────┬────────┘ │
11 | ▼ ▼
12 | ┌─────┐ ┌─────┐ ┌─────┐
13 | │ X │─────▶│ D │─────────────────▶│ E │
14 | └─────┘ └─────┘ └─────┘
15 | ┌─────┴────┐ ┌────┴────┐
16 | ▼ ▼ ▼ ▼
17 | ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
18 | │ L │ │ K │ │ M │ │ F │
19 | └─────┘ └─────┘ └─────┘ └─────┘
20 | */
21 |
22 | class A {}
23 | class X {}
24 | class B { constructor(a: A) {} }
25 | class C { constructor(a: A) {} }
26 | class D { constructor(b: B, c: C, x: X) {} }
27 | class L { constructor(d: D) {} }
28 | class K { constructor(d: D) {} }
29 | class E { constructor(c: C, d: D) {} }
30 | class M { constructor(e: E) {} }
31 | class F { constructor(e: E) {} }
32 |
33 | export { A, X, B, C, D, L, K, E, M, F }
34 |
--------------------------------------------------------------------------------
/iti/tests/mocks/_mock-app-container.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "../../src/iti"
2 |
3 | import { provideAContainer } from "./container.a"
4 | import { provideBContainer } from "./container.b"
5 | import { provideCContainer } from "./container.c"
6 |
7 | export type MockAppNode = ReturnType
8 | export function getMainMockAppContainer() {
9 | let node = createContainer()
10 | let k = node
11 | .add({ aCont: async () => provideAContainer() })
12 | .add((c, node) => {
13 | return {
14 | bCont: async () => provideBContainer(await node.get("aCont")),
15 | }
16 | })
17 | .add((c) => {
18 | return {
19 | cCont: async () => provideCContainer(await c.aCont, await c.bCont, k),
20 | }
21 | })
22 | return k
23 | }
24 |
--------------------------------------------------------------------------------
/iti/tests/mocks/container.a.ts:
--------------------------------------------------------------------------------
1 | import { A1, A2, A3 } from "./store.a"
2 |
3 | export interface A_Container {
4 | a1: A1
5 | a2: A2
6 | a3: A3
7 | }
8 |
9 | export async function provideAContainer(): Promise {
10 | const a1 = new A1()
11 | const a2 = new A2(a1)
12 | const a3 = new A3(a1, a2)
13 |
14 | return {
15 | a1,
16 | a2,
17 | a3,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/iti/tests/mocks/container.b.ts:
--------------------------------------------------------------------------------
1 | import type { A_Container } from "./container.a"
2 | import { B1, B2 } from "./store.b"
3 |
4 | export interface B_Container {
5 | b1: B1
6 | b2: B2
7 | }
8 |
9 | export async function provideBContainer(a: A_Container): Promise {
10 | const b1 = new B1(a.a2)
11 |
12 | const b2 = new B2(a.a1)
13 |
14 | return {
15 | b1,
16 | b2,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/iti/tests/mocks/container.c.ts:
--------------------------------------------------------------------------------
1 | import type { A_Container } from "./container.a"
2 | import { B_Container } from "./container.b"
3 | import { C1, C2 } from "./store.c"
4 | import { MockAppNode } from "./_mock-app-container"
5 | // import { MockAppContainer } from "./_mock-app-container"
6 |
7 | export interface C_Container {
8 | c1: C1
9 | c2: C2
10 | upgradeCContainer: (x?: number) => void
11 | }
12 |
13 | export async function provideCContainer(
14 | a: A_Container,
15 | b: B_Container,
16 | container: MockAppNode,
17 | ): Promise {
18 | const c1 = new C1(a.a2)
19 | const c2 = new C2(a.a1, b.b2, 5)
20 |
21 | async function replacer(ovenSize = 10) {
22 | const c1 = new C1(a.a2)
23 | const c2 = new C2(a.a1, b.b2, ovenSize)
24 | container.upsert(() => ({
25 | cCont: async () => {
26 | return {
27 | c1,
28 | c2,
29 | upgradeCContainer: (ovenSize = 10) => replacer(ovenSize),
30 | }
31 | },
32 | }))
33 | }
34 |
35 | return {
36 | c1,
37 | c2,
38 | upgradeCContainer: replacer,
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iti/tests/mocks/store.a.ts:
--------------------------------------------------------------------------------
1 | export class A1 {
2 | constructor() {}
3 | }
4 | export class A2 {
5 | constructor(private a1: A1) {}
6 | }
7 | export class A3 {
8 | constructor(private a1: A1, private a2: A2) {}
9 | }
10 |
--------------------------------------------------------------------------------
/iti/tests/mocks/store.b.ts:
--------------------------------------------------------------------------------
1 | import type { A1, A2 } from "./store.a"
2 |
3 | export class B1 {
4 | constructor(private a2: A2) {
5 | // 1. Subscribe to event emitter
6 | // 2. Pub update
7 | }
8 | }
9 | export class B2 {
10 | constructor(private a1: A1) {}
11 | }
12 |
--------------------------------------------------------------------------------
/iti/tests/mocks/store.c.ts:
--------------------------------------------------------------------------------
1 | import type { A1, A2 } from "./store.a"
2 | import { B2 } from "./store.b"
3 |
4 | export class C1 {
5 | constructor(private a2: A2) {
6 | // 1. Subscribe to event emitter
7 | // 2. Pub update
8 | }
9 | }
10 | export class C2 {
11 | constructor(private a1: A1, b2: B2, readonly size) {}
12 | }
13 |
--------------------------------------------------------------------------------
/iti/tests/tsd.container-set-types.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach } from "vitest"
2 | import { getMainMockAppContainer } from "./mocks/_mock-app-container"
3 | import { expectType, expectError, printType, expectNotType } from "tsd"
4 | import type { A_Container } from "./mocks/container.a"
5 | import type { B_Container } from "./mocks/container.b"
6 | import type { C_Container } from "./mocks/container.c"
7 |
8 | it("should check token types", () => {
9 | const cont = getMainMockAppContainer()
10 | expectType<{ aCont: "aCont"; bCont: "bCont"; cCont: "cCont" }>(
11 | cont.getTokens(),
12 | )
13 | })
14 |
15 | it("should check getContainerSet types", async () => {
16 | const cont = getMainMockAppContainer()
17 | let containerSet = await cont.getContainerSet(["aCont", "bCont"])
18 | expectNotType(containerSet)
19 | expectNotType(containerSet.aCont)
20 | expectType(containerSet.aCont)
21 | })
22 |
23 | it("should check getContainerSet function types", async () => {
24 | const cont = getMainMockAppContainer()
25 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.bCont])
26 | expectNotType(containerSet)
27 | expectNotType(containerSet.aCont)
28 | expectType(containerSet.aCont)
29 | })
30 |
31 | it("should check subscribe types", async () => {
32 | const cont = getMainMockAppContainer()
33 | cont.subscribeToContainerSet(
34 | (c) => {
35 | expectNotType(c)
36 | expectType<"aCont">(c.aCont)
37 | return [c.aCont, c.cCont]
38 | },
39 | (err, containerSet) => {
40 | expectNotType(containerSet)
41 | expectType(containerSet.aCont)
42 | expectType(containerSet.cCont)
43 | },
44 | )
45 | })
46 |
47 | it("should be able to delete token types", () => {
48 | const cont = getMainMockAppContainer().delete("aCont")
49 | expectNotType(cont.items)
50 | expectType<{ bCont: "bCont"; cCont: "cCont" }>(cont.getTokens())
51 | })
52 |
--------------------------------------------------------------------------------
/iti/tests/tsd.getter.tsd-only.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach } from "vitest"
2 | import { expectType, expectNotType } from "tsd"
3 |
4 | import { createContainer } from "../src/iti"
5 |
6 | enum UniqueResult {
7 | A,
8 | B,
9 | C,
10 | D,
11 | }
12 | // results produced by an add should valid
13 | it("should check getter types", () => {
14 | const node = createContainer()
15 | .add({
16 | a: UniqueResult.A,
17 | b: () => UniqueResult.B,
18 | })
19 | .add(() => ({
20 | c: () => UniqueResult.C,
21 | }))
22 |
23 | expectType(node.get("a"))
24 | expectType(node.get("b"))
25 | expectType(node.get("c"))
26 |
27 | expectNotType(node)
28 | })
29 |
--------------------------------------------------------------------------------
/iti/tests/types-for-tests.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/iti/tests/update.api.vi.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi, beforeEach } from "vitest"
2 | import { createContainer } from "../src/iti"
3 | import { wait } from "./_utils"
4 |
5 | describe("Deleting and destructuring: ", () => {
6 | let root: ReturnType
7 |
8 | beforeEach(() => {
9 | root = createContainer()
10 | })
11 | it("should be able to store null", () => {
12 | let r = root.add({ a: "A", b: null })
13 |
14 | expect(r.get("a")).toBe("A")
15 | expect(r.get("b")).toBe(null)
16 | })
17 |
18 | it("should be able to upsert null and back", () => {
19 | let r = root.add({ a: "A", b: "B" })
20 |
21 | // normal state
22 | expect(r.get("a")).toBe("A")
23 | expect(r.get("b")).toBe("B")
24 |
25 | // upsert null
26 | r.upsert({ b: null })
27 | expect(r.get("b")).toBe(null)
28 |
29 | // tokens should be fine too
30 | expect(r.getTokens()).toMatchObject({ a: "a", b: "b" })
31 |
32 | // update B from null to a Primitive
33 | r.upsert({ b: "new B" })
34 | expect(r.get("b")).toBe("new B")
35 | })
36 |
37 | it("should be able to delete a token", () => {
38 | let r = root.add({ a: "A", b: "B", c: "C" })
39 |
40 | expect(r.getTokens()).toMatchObject({ a: "a", b: "b", c: "c" })
41 |
42 | let updated = r.delete("b")
43 | expect(r.getTokens()).toMatchObject({ a: "a", c: "c" })
44 |
45 | // should throw
46 | expect(() => {
47 | // @ts-expect-error
48 | updated.get("b")
49 | }).toThrow()
50 | })
51 |
52 | it("should send containerUpdated event on overwrite", async () => {
53 | const cb = vi.fn()
54 | root.on("containerDeleted", async (k) => {
55 | expect(k.key).toBe("b")
56 | root.on("containerUpserted", (k) => {
57 | expect(k.key).toBe("b")
58 | expect(k.newContainer).toBe("new B")
59 | cb()
60 | })
61 | await wait(5)
62 | root.upsert({ b: "new B" })
63 | })
64 |
65 | root.add({ a: "A", b: "B" }).delete("b")
66 | await wait(20)
67 | expect(cb).toHaveBeenCalledTimes(1)
68 | })
69 |
70 | it("should send containerUpdated event on overwrite", async () => {
71 | const node = root.add(() => ({
72 | a: "A",
73 | b: "B",
74 | }))
75 | const f1 = vi.fn()
76 | const f2 = vi.fn()
77 |
78 | node.subscribeToContainer("a", f1)
79 | node.subscribeToContainerSet(["a", "b"], f2)
80 |
81 | node.delete("a")
82 |
83 | await wait(10)
84 |
85 | expect(f1).toHaveBeenCalledTimes(1)
86 | /**
87 | * 2 because we have subscribed to two container, and this will provide us
88 | * with two of those, hence two updates because two creations
89 | */
90 | expect(f2).toHaveBeenCalledTimes(1)
91 | })
92 |
93 | it("should send error if we remove a token some container listens to", async () => {
94 | const cb = vi.fn()
95 | const node = root.add(() => ({
96 | a: "A",
97 | b: "B",
98 | }))
99 | node.subscribeToContainer("a", (err) => {
100 | expect(err).not.toBe(null)
101 | cb()
102 | })
103 | node.delete("a")
104 | await wait(10)
105 | expect(cb).toHaveBeenCalledTimes(1)
106 | })
107 |
108 | it("should send error if we remove a token some containerSet listens to", async () => {
109 | const cb = vi.fn()
110 | const node = root.add(() => ({
111 | a: "A",
112 | b: "B",
113 | }))
114 | node.subscribeToContainerSet(["a", "b"], (err) => {
115 | expect(err).not.toBe(null)
116 | cb()
117 | })
118 | node.delete("a")
119 | await wait(10)
120 | expect(cb).toHaveBeenCalledTimes(1)
121 | })
122 | })
123 |
--------------------------------------------------------------------------------
/iti/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "baseUrl": "./src",
5 | "outDir": "dist/",
6 | "declarationDir": "dist/",
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | // "types": ["@types/jest"],
9 | "noImplicitAny": false,
10 | "experimentalDecorators": true,
11 | "allowJs": true,
12 | "skipLibCheck": true,
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "strict": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "module": "esnext",
19 | "moduleResolution": "node",
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "declaration": true,
23 | "jsx": "react"
24 | },
25 | "include": ["src/**/*"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/iti/tsd_project/dummy.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/iti/tsd_project/dummy.d.ts
--------------------------------------------------------------------------------
/iti/tsd_project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": "dummy.d.ts",
3 | "tsd": {
4 | "directory": "../tests"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iti/vitest.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { defineConfig } from "vite"
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: false,
7 | include: ["**/*.vi.{test,spec}.?(c|m)[jt]s?(x)"],
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-workspace",
3 | "version": "0.0.1",
4 | "description": "iti yarn monorepo root",
5 | "private": true,
6 | "workspaces": [
7 | "iti",
8 | "iti-react",
9 | "examples/cra",
10 | "examples/node-cli",
11 | "examples/vite-app"
12 | ],
13 | "scripts": {
14 | "test": "turbo run test",
15 | "build": "turbo run build"
16 | },
17 | "devDependencies": {
18 | "turbo": "^1.8.3"
19 | },
20 | "author": "Nick Olszanski ",
21 | "license": "MIT",
22 | "packageManager": "yarn@1.22.17",
23 | "dependencies": {
24 | "jest-environment-jsdom": "^29.7.0",
25 | "prettier": "^2.7.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turborepo.org/schema.json",
3 | "baseBranch": "origin/main",
4 | "pipeline": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": [".next/**"]
8 | },
9 | "test": {
10 | "dependsOn": ["^build"],
11 | "outputs": []
12 | },
13 | "lint": {
14 | "outputs": []
15 | },
16 | "dev": {
17 | "cache": false
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
4 |
5 | ### Installation
6 |
7 | ```
8 | $ yarn
9 | ```
10 |
11 | ### Local Development
12 |
13 | ```
14 | $ yarn start
15 | ```
16 |
17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ### Build
20 |
21 | ```
22 | $ yarn build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ### Deployment
28 |
29 | Using SSH:
30 |
31 | ```
32 | $ USE_SSH=true yarn deploy
33 | ```
34 |
35 | Not using SSH:
36 |
37 | ```
38 | $ GIT_USER= yarn deploy
39 | ```
40 |
41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
42 |
--------------------------------------------------------------------------------
/website/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/website/blog/2019-05-28-first-blog-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: first-blog-post
3 | title: First Blog Post
4 | authors:
5 | name: Gao Wei
6 | title: Docusaurus Core Team
7 | url: https://github.com/wgao19
8 | image_url: https://github.com/wgao19.png
9 | tags: [hola, docusaurus]
10 | ---
11 |
12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
13 |
--------------------------------------------------------------------------------
/website/blog/2019-05-29-long-blog-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: long-blog-post
3 | title: Long Blog Post
4 | authors: endi
5 | tags: [hello, docusaurus]
6 | ---
7 |
8 | This is the summary of a very long blog post,
9 |
10 | Use a `` comment to limit blog post size in the list view.
11 |
12 |
13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
15 |
16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
17 |
18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
19 |
20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
21 |
22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
23 |
24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
25 |
26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
29 |
30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
31 |
32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
33 |
34 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
35 |
36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
37 |
38 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
39 |
40 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
43 |
44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
45 |
--------------------------------------------------------------------------------
/website/blog/2021-08-01-mdx-blog-post.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | slug: mdx-blog-post
3 | title: MDX Blog Post
4 | authors: [slorber]
5 | tags: [docusaurus]
6 | ---
7 |
8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
9 |
10 | :::tip
11 |
12 | Use the power of React to create interactive blog posts.
13 |
14 | ```js
15 |
16 | ```
17 |
18 |
19 |
20 | :::
21 |
--------------------------------------------------------------------------------
/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg
--------------------------------------------------------------------------------
/website/blog/2021-08-26-welcome/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: welcome
3 | title: Welcome
4 | authors: [slorber, yangshun]
5 | tags: [facebook, hello, docusaurus]
6 | ---
7 |
8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
9 |
10 | Simply add Markdown files (or folders) to the `blog` directory.
11 |
12 | Regular blog authors can be added to `authors.yml`.
13 |
14 | The blog post date can be extracted from filenames, such as:
15 |
16 | - `2019-05-30-welcome.md`
17 | - `2019-05-30-welcome/index.md`
18 |
19 | A blog post folder can be convenient to co-locate blog post images:
20 |
21 | 
22 |
23 | The blog supports tags as well!
24 |
25 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
26 |
--------------------------------------------------------------------------------
/website/blog/authors.yml:
--------------------------------------------------------------------------------
1 | endi:
2 | name: Endilie Yacop Sucipto
3 | title: Maintainer of Docusaurus
4 | url: https://github.com/endiliey
5 | image_url: https://github.com/endiliey.png
6 |
7 | yangshun:
8 | name: Yangshun Tay
9 | title: Front End Engineer @ Facebook
10 | url: https://github.com/yangshun
11 | image_url: https://github.com/yangshun.png
12 |
13 | slorber:
14 | name: Sébastien Lorber
15 | title: Docusaurus maintainer
16 | url: https://sebastienlorber.com
17 | image_url: https://github.com/slorber.png
18 |
--------------------------------------------------------------------------------
/website/docs/1.intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Intro
6 |
7 | Iti is 1kB Typesafe dependency injection framework for TypeScript and JavaScript with a unique support for **async flow**
8 |
9 | _This library doesn't try be scientifically correct. I just want to go home early_
10 |
11 | ## Features
12 |
13 | - **supports async(!) dependencies:** merges async code and constructor injection via plain **async** functions
14 | - **strongly typed:** has great IDE autocomplete and compile time check. Without any [manual type casting](https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#containergettserviceidentifier-interfacesserviceidentifiert-t)
15 | - **non-invasive:** does not require imported `@decorators` or framework `extends` in your application business logic
16 | - **lazy:** initializes your app modules and containers on demand
17 | - **split chunks:** enables **[dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports)** via a [one liner](#dynamic-imports) thanks to a fully async core
18 | - **React friendly:** has useful **[React](https://github.com/molszanski/iti/tree/master/iti-react)** bindings to help you separate application business logic and a React view layer
19 | - **starter friendly:** works with starters like [Create React App](https://create-React-app.dev/) or [Next.js](https://nextjs.org/docs/getting-started) unlike existing libraries
20 | - **no Babel config:** doesn't require `reflect-metadata` or decorators so there is no need to hack in decorator and `"decoratorMetadata"` support in to your build configs
21 | - **tiny:** less than 1kB
22 |
23 | IoC is an amazing pattern and it should **easy to adopt**, fully support async and without hard to learn APIs or complex tooling requirements.
24 |
25 | Iti relies on plain JS functions, objects and familiar patterns. API is simple so you can make a **proof of concept integration in minutes**.
26 |
27 | It is an alternative to [InversifyJS](https://github.com/inversify/InversifyJS) and [microsoft/tsyringe](https://github.com/microsoft/tsyringe) for constructor injection.
28 |
29 | > _At [Packhelp](https://unpacked.packhelp.com) we’ve refactored most of our 65K SLOC Editor app, that didn't have any IoC, to Iti in under 5 hours_
30 |
--------------------------------------------------------------------------------
/website/docs/10.faq.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 11
3 | slug: faq
4 | ---
5 |
6 | import TOCInline from "@theme/TOCInline"
7 |
8 | import CodeBlock from "@theme/CodeBlock"
9 | import circularDependency from "!!raw-loader!./examples/circular-dependency.ts"
10 |
11 | # FAQ
12 |
13 | :::note
14 |
15 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it.
16 |
17 | :::
18 |
19 |
20 |
21 | ## Questions
22 |
23 | **Can I have multiple application containers?**
24 |
25 | Yes, no problem at all. If you want, they can even share tokens and hence instances!
26 |
27 | **Why `getContainerSet` is always async?**
28 |
29 | This is temporary(?) limitation to keep typescript happy and typescript types reasonably sane.
30 | In most real world scenarios your frontend dependencies are already async.
31 |
32 | ### Why should I use ITI?
33 |
34 | We strongly believe that helps to implement good DI patterns in your codebase and offers
35 | better tradeoffs compared to alternative DI frameworks or solutions.
36 | Check our [alternatives](/docs/alternatives) section
37 |
38 | ### How does handle circular dependency?
39 |
40 | You can not create a circular dependency with iti and typescript.
41 | It will throw a typescript error if you try :)
42 |
43 | {circularDependency}
44 |
--------------------------------------------------------------------------------
/website/docs/2.quick-start.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | import CodeBlock from "@theme/CodeBlock"
6 | import withIti from "!!raw-loader!./6_async_start-PART-2/main/with.tsx"
7 | import without from "!!raw-loader!./6_async_start-PART-2/main/without.ts"
8 | import quickStartCode from "!!raw-loader!./6_async_start-PART-2/main/business-logic.ts"
9 | import wiringCode from "!!raw-loader!./6_async_start-PART-2/main/app.ts"
10 |
11 | # Quick Start
12 |
13 | ITI is a 1kb dependency injections framework for asynchronous applications.
14 | You are probably using some form of manual DI and ITI is a logical upgrade.
15 |
16 | If you are building a react there are useful react bindings are available.
17 |
18 | :::note
19 |
20 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it.
21 |
22 | :::
23 |
24 | ## Adding ITI to your Project
25 |
26 | ```
27 | # with npm
28 | npm install -S iti iti-react
29 |
30 | # or with yarn
31 | yarn add iti iti-react
32 | ```
33 |
34 | :::tip
35 |
36 | If you are building a React App there are useful React bindings (`iti-react`) are available.
37 |
38 | :::
39 |
40 | ## TL;DR
41 |
42 | **with ITI**
43 |
44 | {withIti}
45 |
46 | **Without ITI**
47 |
48 | {without}
49 |
50 | ## Usage
51 |
52 | ```tsx
53 | // (Optional) With React
54 | import { useContainer } from "./_containers/main-app"
55 |
56 | function Profile() {
57 | const [user, userErr] = useContainer().userData
58 | if (!user || userErr) return loading... or error
59 |
60 | return Hello {user.name}!
61 | }
62 | ```
63 |
64 | {quickStartCode}
65 | {wiringCode}
66 |
67 | ```tsx
68 | // Part 3: Usage
69 | import { app } from "./app"
70 |
71 | // Will lazily fetch data and create PaymentService instance
72 | const paymentService = await app.items.paymentService
73 | paymentService.sendMoney()
74 | ```
75 |
76 | ```tsx
77 | // (Optional) With React
78 | import { useContainer } from "./_containers/main-app"
79 |
80 | function Profile() {
81 | const [user, userErr] = useContainer().userData
82 | if (!user || userErr) return loading... or error
83 |
84 | return Hello {user.name}!
85 | }
86 | ```
87 |
--------------------------------------------------------------------------------
/website/docs/4.usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | title: Usage
4 | ---
5 |
6 | :::note
7 |
8 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it.
9 |
10 | :::
11 |
12 | # Short Manual
13 |
14 | ## Reading and Writing data
15 |
16 | **Reading**
17 |
18 | ```ts
19 | // Get a single instance
20 | root.get("oven") // Creates a new Oven instance
21 | root.get("oven") // Gets a cached Oven instance
22 |
23 | await node.get("kitchen") // { kitchen: Kitchen } also cached
24 | await node.items.kitchen // same as above
25 |
26 | // Plain deletion
27 | node.delete("kitchen")
28 |
29 | // Get multiple instances at once
30 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven }
31 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above
32 |
33 | // Subscribe to container changes
34 | node.subscribeToContainer("oven", (oven) => {})
35 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {})
36 | // prettier-ignore
37 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {})
38 | node.on("containerUpdated", ({ key, newItem }) => {})
39 | node.on("containerUpserted", ({ key, newItem }) => {})
40 | node.on("containerDeleted", ({ key, newItem }) => {})
41 |
42 | // Disposing
43 | node
44 | .add({ dbConnection: () => connectToDb(process.env.dbUrl) })
45 | .addDisposer({ dbConnection: (db) => db.disconnect() }) // waits for promise
46 | await node.dispose("dbConnection")
47 | await node.disposeAll()
48 | ```
49 |
50 | **Writing**
51 |
52 | ```ts
53 | let node1 = createContainer()
54 | .add({
55 | userManual: "Please preheat before use",
56 | oven: () => new Oven(),
57 | })
58 | .upsert((containers, node) => ({
59 | userManual: "Works better when hot",
60 | preheatedOven: async () => {
61 | await containers.oven.preheat()
62 | return containers.oven
63 | },
64 | }))
65 |
66 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert`
67 | try {
68 | node1.add({
69 | // @ts-expect-error
70 | userManual: "You shall not pass",
71 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method"
72 | })
73 | } catch (err) {
74 | err.message // Error Tokens already exist: ['userManual']
75 | }
76 | ```
77 |
--------------------------------------------------------------------------------
/website/docs/5.patterns-and-tips.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | title: Patterns and Tips
4 | ---
5 |
6 | :::note
7 |
8 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it.
9 |
10 | :::
11 |
12 | # Patterns and Tips
13 |
14 | ## Patterns and tips
15 |
16 | ### Make lazy simple
17 |
18 | Prefer functions over eager init. Why? This will make the app lazy, hence faster.
19 |
20 | ```ts
21 | // Good
22 | createContainer().add({
23 | eventBus: () => new EventBus(),
24 | userAuthService: () => new UserAuthService(),
25 | })
26 |
27 | // Meh...
28 | createContainer().add({
29 | eventBus: new EventBus(),
30 | userAuthService: new UserAuthService(),
31 | })
32 | ```
33 |
34 | In the second example we create instances on IoC container start. Which in most cases is not desirable. With the first example, we use functions, and they will be executed only when requested
35 |
36 | ### Lifecycle
37 |
38 | **Single Instance (a.k.a. Singleton)**
39 |
40 | ```ts
41 | let node = createContainer().add({
42 | oven: () => new Oven(),
43 | })
44 | node.get("oven") === node.get("oven") // true
45 | ```
46 |
47 | **Transient**
48 |
49 | ```ts
50 | let node = createContainer().add({
51 | oven: () => () => new Oven(),
52 | })
53 | node.get("oven") === node.get("oven") // false
54 | ```
55 |
56 | ### Dynamic Imports
57 |
58 | ```ts
59 | // ./kitchen/index.ts
60 | export async function provideKitchenContainer() {
61 | const { Kitchen } = await import("./kitchen/kitchen")
62 | return {
63 | kitchen: () => new Kitchen(),
64 | oven: async () => {
65 | const { Oven } = await import("./kitchen/oven")
66 | const oven = new Oven()
67 | await oven.preheat()
68 | return oven
69 | },
70 | }
71 | }
72 | ```
73 |
74 | ```ts
75 | // ./index.ts
76 | import { createContainer } from "iti"
77 | import { provideKitchenContainer } from "./kitchen"
78 | let node = createContainer().add({
79 | kitchen: async () => provideKitchenContainer(),
80 | })
81 |
82 | // Next line will load `./kitchen/kitchen` module
83 | await node.items.kitchen
84 |
85 | // Next line will load `./kitchen/oven` module
86 | await node.items.kitchen.oven
87 | ```
88 |
89 | ### Tip: Prefer callbacks over of strings (in progress)
90 |
91 | If you use callback pattern across your app, you will be able to mass rename your containerKeys using typescript. With strings, you will have to manually go through the app. But even if you use string literals compiler will not compile until you fix your rename manually across the app.
92 |
93 | ```ts
94 | const node = createContainer().addNode({
95 | a: "A",
96 | b: "B",
97 | })
98 |
99 | await node.get((containerKeys) => containerKeys.a) // BEST!!!
100 | await node.get("a") // it will work but...
101 | ```
102 |
103 | ## Anti Patterns
104 |
105 | in progress
106 |
107 | ## Known issues
108 |
109 | ### TS2589: Type instantiation is excessively deep and possibly infinite
110 |
111 | This bug is caused by a TS hard limit on 50 `instantiationDepth`.
112 |
113 | https://github.com/i18next/react-i18next/issues/1417
114 | https://github.com/microsoft/TypeScript/issues/34933
115 |
116 | As a quick workaround we suggest:
117 |
118 | 1. **Reduce the number of `.add` steps** - this will help in most cases
119 | 2. **Reduce the number of unique tokens** - group some tokens together
120 | 3. **Create multiple containers** - it seems that your app is getting pretty big and complex. Maybe create to 2 containers via `createContainer`?
121 | 4. **Upgrade to TS 4.5 or higher**
122 | 5. **Optimize ITI**
123 |
--------------------------------------------------------------------------------
/website/docs/6.getting-started.md.del:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 6
3 | ---
4 |
5 | # Getting Started
6 |
7 |
8 | ## Getting Started
9 |
10 | The best way to get started is to check [a CRA Pizza example](https://github.com/molszanski/iti/tree/master/examples/cra/src/containers)
11 |
12 | Initial wiring
13 |
14 | ```ts
15 | import { createContainer } from "../../src/library.new-root-container"
16 |
17 | import { provideAContainer } from "./container.a"
18 | import { provideBContainer } from "./container.b"
19 | import { provideCContainer } from "./container.c"
20 |
21 | export type MockAppNode = ReturnType
22 | export function getMainMockAppContainer() {
23 | return createContainer()
24 | .add({ aCont: async () => provideAContainer() })
25 | .add((containers) => {
26 | return {
27 | bCont: async () => provideBContainer(await containers.aCont),
28 | }
29 | })
30 | .add((c) => {
31 | return {
32 | cCont: async () => provideCContainer(await c.aCont, await c.bCont, k),
33 | }
34 | })
35 | }
36 | ```
37 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/0_by_hand.ts:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 | interface Logger { info: (msg: string) => void }
3 | // prettier-ignore
4 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
5 | // prettier-ignore
6 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
7 |
8 | // Part 1: Business Entities
9 | interface UserData {
10 | name: string
11 | }
12 |
13 | class AuthService {
14 | async getUserData(): Promise {
15 | return { name: "Big Lebowski" }
16 | }
17 | }
18 |
19 | class User {
20 | constructor(private data: UserData) {}
21 | name = () => this.data.name
22 | }
23 |
24 | class PaymentService {
25 | constructor(private readonly logger: Logger, private readonly user: User) {}
26 | sendMoney() {
27 | this.logger.info(`Sending monery to the: ${this.user.name()} `)
28 | return true
29 | }
30 | }
31 |
32 | // Step 2: Manual DI
33 | export async function runMyApp() {
34 | const logger =
35 | process.env.NODE_ENV === "production"
36 | ? new PinoLogger()
37 | : new ConsoleLogger()
38 |
39 | const auth = new AuthService()
40 | const user = new User(await auth.getUserData())
41 |
42 | const paymentService = new PaymentService(logger, user)
43 | paymentService.sendMoney()
44 | }
45 |
46 | console.log(" ---- My App START \n\n")
47 | runMyApp().then(() => {
48 | console.log("\n\n ---- My App END")
49 | })
50 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/1_ts-inject.ts:
--------------------------------------------------------------------------------
1 | import { createInjector } from "typed-inject"
2 | // prettier-ignore
3 | interface Logger { info: (msg: string) => void }
4 | // prettier-ignore
5 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
6 | // prettier-ignore
7 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
8 |
9 | interface UserData {
10 | name: string
11 | }
12 |
13 | class AuthService {
14 | async getUserData(): Promise {
15 | return { name: "Big Lebowski" }
16 | }
17 | }
18 |
19 | const Token = Object.freeze({
20 | logger: "logger",
21 | consoleLogger: "consoleLogger",
22 | pinoLogger: "pinoLogger",
23 | authService: "authService",
24 | user: "user",
25 | userData: "userData",
26 | paymentService: "paymentService",
27 | } as const)
28 |
29 | class User {
30 | public static inject = [Token.userData] as const
31 | constructor(private data: UserData) {
32 | console.log("constructing user")
33 | }
34 | name = () => this.data.name
35 | }
36 |
37 | class PaymentService {
38 | public static inject = [Token.logger, Token.user] as const
39 | constructor(private readonly logger: Logger, private readonly user: User) {}
40 | sendMoney() {
41 | this.logger.info(`Sending monery to the: ${this.user.name()} `)
42 | return true
43 | }
44 | }
45 |
46 | /// INJECTING
47 |
48 | export async function runMyApp() {
49 | const container = createInjector()
50 | .provideFactory("logger", () =>
51 | process.env.NODE_ENV === "production"
52 | ? new PinoLogger()
53 | : new ConsoleLogger()
54 | )
55 | .provideClass(Token.authService, AuthService)
56 |
57 | const authService = container.resolve(Token.authService)
58 | const userData = await authService.getUserData()
59 |
60 | const container2 = container
61 | .provideValue(Token.userData, userData)
62 | .provideClass(Token.user, User)
63 | .provideClass(Token.paymentService, PaymentService)
64 |
65 | const ps = container2.resolve(Token.paymentService)
66 | ps.sendMoney()
67 | }
68 |
69 | console.log(" ---- My App START \n\n")
70 | runMyApp().then(() => {
71 | console.log("\n\n ---- My App END")
72 | })
73 |
74 | /**
75 | * There is another option where we add an async function
76 | * to a factory like this:
77 | *
78 | * container.provideFactory('user', async () => { name: 'Alice' })
79 | *
80 | * but then all typings go crazy and basically
81 | * we write in JS :/
82 | */
83 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/2_tsyringe.ts:
--------------------------------------------------------------------------------
1 | import "reflect-metadata"
2 | import {
3 | container,
4 | injectable,
5 | inject,
6 | singleton,
7 | predicateAwareClassFactory,
8 | } from "tsyringe"
9 | // prettier-ignore
10 | interface Logger { info: (msg: string) => void }
11 | // prettier-ignore
12 | @injectable()
13 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
14 | // prettier-ignore
15 | @injectable()
16 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
17 |
18 | interface UserData {
19 | name: string
20 | }
21 |
22 | @injectable()
23 | class AuthService {
24 | async getUserData(): Promise {
25 | return { name: "Big Lebowski" }
26 | }
27 | }
28 |
29 | const Token = Object.freeze({
30 | logger: "logger",
31 | consoleLogger: "consoleLogger",
32 | pinoLogger: "pinoLogger",
33 | authService: "authService",
34 | user: "user",
35 | userData: "userData",
36 | paymentService: "paymentService",
37 | } as const)
38 |
39 | // @singleton()
40 | @injectable()
41 | class User {
42 | constructor(@inject(Token.userData) private data: UserData) {
43 | console.log("constructing user")
44 | }
45 | name = () => this.data.name
46 | }
47 |
48 | @injectable()
49 | class PaymentService {
50 | constructor(
51 | @inject(Token.logger) private readonly logger: Logger,
52 | @inject(User) private readonly user: User
53 | ) {}
54 | sendMoney() {
55 | this.logger.info(`Sending monery to the: ${this.user.name()} `)
56 | return true
57 | }
58 | }
59 |
60 | /// INJECTING
61 |
62 | export async function runMyApp() {
63 | container.register(Token.logger, {
64 | useFactory: predicateAwareClassFactory(
65 | (c) => {
66 | // c.resolve(PinoLogger)
67 | return process.env.NODE_ENV === "production"
68 | },
69 | ConsoleLogger,
70 | PinoLogger
71 | ),
72 | })
73 |
74 | // Danger zone 1
75 | const auth = container.resolve(AuthService)
76 | const d = await auth.getUserData()
77 | container.register(Token.userData, { useValue: d })
78 |
79 | // Dange zone 2
80 | container.resolve(User)
81 | container.resolve(User)
82 |
83 | const ps = container.resolve(PaymentService)
84 | ps.sendMoney()
85 | }
86 |
87 | console.log(" ---- My App START \n\n")
88 | runMyApp().then(() => {
89 | console.log("\n\n ---- My App END")
90 | })
91 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main-example.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 | // prettier-ignore
3 | interface Logger { info: (msg: string) => void }
4 | // prettier-ignore
5 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
6 | // prettier-ignore
7 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
8 |
9 | // Part 1: Business Entities
10 | interface UserData {
11 | name: string
12 | }
13 |
14 | class AuthService {
15 | async getUserData(): Promise {
16 | return { name: "Big Lebowski" }
17 | }
18 | }
19 |
20 | class PaymentService {
21 | constructor(
22 | private readonly logger: Logger,
23 | private readonly user: UserData
24 | ) {}
25 | sendMoney() {
26 | this.logger.info(`Sending money to the: ${this.user.name} `)
27 | return true
28 | }
29 | }
30 |
31 | // Step 2: Manual DI
32 | export async function runMyApp() {
33 | const logger =
34 | process.env.NODE_ENV === "production"
35 | ? new PinoLogger()
36 | : new ConsoleLogger()
37 |
38 | const auth = new AuthService()
39 | const userData = await auth.getUserData()
40 |
41 | const paymentService = new PaymentService(logger, userData)
42 | paymentService.sendMoney()
43 | }
44 |
45 | export async function runMyApp2() {
46 | const root = createContainer()
47 | .add({
48 | logger: () =>
49 | process.env.NODE_ENV === "production"
50 | ? new PinoLogger()
51 | : new ConsoleLogger(),
52 | auth: () => new AuthService(),
53 | })
54 | .add((ctx) => ({
55 | paymentService: async () =>
56 | new PaymentService(ctx.logger, await ctx.auth.getUserData()),
57 | }))
58 |
59 | const paymentService = await root.items.paymentService
60 | paymentService.sendMoney()
61 | }
62 |
63 | console.log(" ---- My App START \n\n")
64 | runMyApp().then(() => {
65 | console.log("\n\n ---- My App END")
66 | })
67 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/app.ts:
--------------------------------------------------------------------------------
1 | // Part 2: ITI boilerplate. Manual DI alternative
2 | import { createContainer } from "iti"
3 | import {
4 | PaymentService,
5 | AuthService,
6 | CookieStorageService,
7 | } from "./business-logic"
8 | import { PinoLogger, ConsoleLogger } from "./loggers"
9 |
10 | export const app = createContainer()
11 | .add({
12 | // Add token `logger` and assign some logger instance
13 | logger: () =>
14 | process.env.NODE_ENV === "production"
15 | ? new PinoLogger()
16 | : new ConsoleLogger(),
17 | // Add token `cookieStorage` ...
18 | cookieStorage: () => new CookieStorageService(),
19 | })
20 | .add((ctx) => ({
21 | auth: () => new AuthService(ctx.cookieStorage),
22 | }))
23 | .add((ctx) => ({
24 | userData: async () => await ctx.auth.getUserData(),
25 | }))
26 | .add((ctx) => ({
27 | paymentService: async () =>
28 | new PaymentService(ctx.logger, await ctx.userData),
29 | }))
30 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/business-logic.ts:
--------------------------------------------------------------------------------
1 | // Part 1: Normal Application Business Logic
2 | interface Logger {
3 | info: (msg: string) => void
4 | }
5 |
6 | interface UserData {
7 | name: string
8 | }
9 |
10 | export class CookieStorageService {
11 | async getSessionToken(): Promise<{ token: string }> {
12 | return { token: "magicToken123" }
13 | }
14 | }
15 |
16 | export class AuthService {
17 | constructor(private cookieStorageService: CookieStorageService) {}
18 | async getUserData(): Promise {
19 | const { token } = await this.cookieStorageService.getSessionToken()
20 | if (token === "magicToken123") {
21 | return { name: "Big Lebowski" }
22 | }
23 | throw new Error("Unauthorized")
24 | }
25 | }
26 |
27 | export class PaymentService {
28 | constructor(
29 | private readonly logger: Logger,
30 | private readonly user: UserData
31 | ) {}
32 | sendMoney() {
33 | this.logger.info(`Sending money to the: ${this.user.name} `)
34 | return true
35 | }
36 | }
37 |
38 | // Application code is free of framework dependencies like decorators
39 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/loggers.ts:
--------------------------------------------------------------------------------
1 | interface Logger {
2 | info: (msg: string) => void
3 | }
4 | export class ConsoleLogger implements Logger {
5 | info(msg: string): void {
6 | console.log("[Console]:", msg)
7 | }
8 | }
9 | export class PinoLogger implements Logger {
10 | info(msg: string): void {
11 | console.log("[Pino]:", msg)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/manual-di.ts:
--------------------------------------------------------------------------------
1 | const logger =
2 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger()
3 |
4 | const auth = new AuthService()
5 | const userData = await auth.getUserData()
6 |
7 | const paymentService = new PaymentService(logger, userData)
8 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/with.tsx:
--------------------------------------------------------------------------------
1 | import { app } from "./app"
2 |
3 | // Proxy Getter: Lazily creates PaymentService instance
4 | const paymentService = await app.items.paymentService
5 | paymentService.sendMoney()
6 |
--------------------------------------------------------------------------------
/website/docs/6_async_start-PART-2/main/without.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PaymentService,
3 | AuthService,
4 | CookieStorageService,
5 | } from "./business-logic"
6 | import { PinoLogger, ConsoleLogger } from "./loggers"
7 |
8 | const logger =
9 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger()
10 |
11 | const app = async () => {
12 | const cookieStorage = new CookieStorageService()
13 | const auth = new AuthService(cookieStorage)
14 | const userData = await auth.getUserData()
15 | const paymentService = new PaymentService(logger, userData)
16 |
17 | return {
18 | paymentService,
19 | }
20 | }
21 |
22 | app().then(({ paymentService }) => {
23 | paymentService.sendMoney()
24 | })
25 |
--------------------------------------------------------------------------------
/website/docs/7.5.playground.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 7
3 | slug: playground
4 | title: Playground
5 | ---
6 |
7 | # Playground
8 |
9 | You can launch a live browser [playground on Stackblitz website][1] or `checkout` the
10 | [playground repo](https://github.com/molszanski/iti-playground)
11 |
12 | ```bash
13 | git clone git@github.com:molszanski/iti-playground.git
14 | cd iti-playground/src/
15 | ```
16 |
17 | [Try it out live (on Stacklitz, an instant browser dev environment )][1]
18 |
19 |
20 |
21 | 
22 |
23 |
24 |
25 | https://stackblitz.com/github/molszanski/iti-playground/tree/main
26 |
27 | [0]: https://michel.codes/blogs/ui-as-an-afterthought
28 | [1]: https://stackblitz.com/github/molszanski/iti-playground/tree/main?file=src%2F_0.business-logic.ts&file=src%2FApp.tsx
29 |
--------------------------------------------------------------------------------
/website/docs/8.benefits-of-iti.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 6
3 | ---
4 |
5 | # Benefits
6 |
7 | _work in progress_
8 |
9 | ## Typescript
10 |
11 | Iti has a great typescript support. All types are resolved automatically and check at compile time.
12 |
13 | 
14 | 
15 | 
16 | 
17 |
--------------------------------------------------------------------------------
/website/docs/9.api/addDisposer.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 | import { Client } from "pg"
3 |
4 | class A {}
5 |
6 | createContainer()
7 | .add({
8 | db: async () => {
9 | const pg = new Client(process.env["DB_CONNECTION_URL"])
10 | await pg.connect()
11 | return pg
12 | },
13 | })
14 | .add((ctx) => ({
15 | a: () => new A(),
16 | b: async () => {
17 | const db = await ctx.db
18 | db.query("SELECT 1")
19 | },
20 | }))
21 | // ↓ `ctx` to access any other dependency
22 | .addDisposer((ctx) => ({
23 | // ↓ `db` is a resolved value of a `DB`
24 | db: (db) => {
25 | console.log(ctx.a)
26 | return db.disconnect()
27 | },
28 | }))
29 |
30 | class B {}
31 | const container = createContainer()
32 | .add({
33 | a: () => new A(),
34 | b: () => new B(),
35 | })
36 | .addDisposer({
37 | a: (a) => console.log("disposing a", a),
38 | })
39 |
40 | container.dispose("a")
41 |
--------------------------------------------------------------------------------
/website/docs/9.api/disposeAll.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "pg"
2 | import { createContainer } from "iti"
3 |
4 | const container = createContainer()
5 | .add(() => ({
6 | dbConnection: async () => {
7 | const pg = new Client(process.env["DB_CONNECTION_URL"])
8 | await pg.connect()
9 | return pg
10 | },
11 | }))
12 | .addDisposer({
13 | dbConnection: (dbConnection) => dbConnection.end(),
14 | })
15 |
16 | const db = await container.get("dbConnection")
17 | await db.query("...")
18 |
19 | // Later..
20 | await container.disposeAll()
21 |
22 | console.log("All dependencies disposed, you can exit now. :)")
23 |
--------------------------------------------------------------------------------
/website/docs/assets/stackblitz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/stackblitz.png
--------------------------------------------------------------------------------
/website/docs/assets/ts/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/1.png
--------------------------------------------------------------------------------
/website/docs/assets/ts/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/2.png
--------------------------------------------------------------------------------
/website/docs/assets/ts/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/3.png
--------------------------------------------------------------------------------
/website/docs/assets/ts/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/4.png
--------------------------------------------------------------------------------
/website/docs/async-di/1.manual-di.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | import CodeBlock from "@theme/CodeBlock"
6 | import manualDi from "!!raw-loader!../6_async_start-PART-2/0_by_hand.ts"
7 |
8 | # Pure DI
9 |
10 | {manualDi}
11 |
--------------------------------------------------------------------------------
/website/docs/async-di/3.syringe.mdx.hidden:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Syringe
6 |
7 | _work in progress_
8 |
--------------------------------------------------------------------------------
/website/docs/async-di/4.typed-inject.mdx.hi:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | ---
4 |
5 | # Typed Inject
6 |
7 | _work in progress_
8 |
--------------------------------------------------------------------------------
/website/docs/async-di/5.iti.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | tags:
4 | - Demo
5 | - Getting started
6 | ---
7 |
8 | # ITI
9 |
10 | ```ts
11 | import { createContainer } from "iti"
12 | // prettier-ignore
13 | interface Logger { info: (msg: string) => void }
14 | // prettier-ignore
15 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } }
16 | // prettier-ignore
17 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } }
18 |
19 | interface UserData {
20 | name: string
21 | }
22 |
23 | class AuthService {
24 | async getUserData(): Promise {
25 | return { name: "Big Lebowski" }
26 | }
27 | }
28 |
29 | class User {
30 | constructor(private data: UserData) {}
31 | name = () => this.data.name
32 | }
33 |
34 | class PaymentService {
35 | constructor(private readonly logger: Logger, private readonly user: User) {}
36 | sendMoney() {
37 | this.logger.info(`Sending monery to the: ${this.user.name()} `)
38 | return true
39 | }
40 | }
41 |
42 | export async function runMyApp() {
43 | const root = createContainer()
44 | .add({
45 | logger: () =>
46 | process.env.NODE_ENV === "production"
47 | ? new PinoLogger()
48 | : new ConsoleLogger(),
49 | })
50 | .add({ auth: new AuthService() })
51 | .add((ctx) => ({
52 | user: async () => new User(await ctx.auth.getUserData()),
53 | }))
54 | .add((ctx) => ({
55 | paymentService: async () =>
56 | new PaymentService(ctx.logger, await ctx.user),
57 | }))
58 |
59 | const ps = await root.items.paymentService
60 | ps.sendMoney()
61 | }
62 |
63 | console.log(" ---- My App START \n\n")
64 | runMyApp().then(() => {
65 | console.log("\n\n ---- My App END")
66 | })
67 | ```
68 |
--------------------------------------------------------------------------------
/website/docs/async-di/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Async DI Example",
3 | "position": 40,
4 | "link": {
5 | "type": "generated-index"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/website/docs/basic-di/1.manual-di.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Pure DI
6 |
7 | ```ts
8 | export interface Logger {
9 | info: (msg: string) => void
10 | warn: (msg: string) => void
11 | error: (msg: string) => void
12 | }
13 |
14 | export class ConsoleLogger implements Logger {
15 | info(msg: string): void {
16 | console.log("Console Logger: ", msg)
17 | }
18 | warn(msg: string): void {}
19 | error(msg: string): void {}
20 | }
21 |
22 | export class PinoLogger implements Logger {
23 | info(msg: string): void {
24 | console.log("Pino Logger: ", msg)
25 | }
26 | warn(msg: string): void {}
27 | error(msg: string): void {}
28 | }
29 |
30 | export class PaymentService {
31 | private readonly logger: Logger
32 | constructor(_logger: Logger) {
33 | this.logger = _logger
34 | }
35 |
36 | sendMoney() {
37 | this.logger.info("inversify logger info")
38 | return true
39 | }
40 | }
41 |
42 | const logger =
43 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger()
44 |
45 | const paymentService = new PaymentService(logger)
46 | paymentService.sendMoney()
47 | ```
48 |
--------------------------------------------------------------------------------
/website/docs/basic-di/2.inversify.mdx.hidden:
--------------------------------------------------------------------------------
1 | _work in progress_
2 |
--------------------------------------------------------------------------------
/website/docs/basic-di/3.syringe.mdx.hidden:
--------------------------------------------------------------------------------
1 | _work in progress_
2 |
--------------------------------------------------------------------------------
/website/docs/basic-di/4.typed-inject.mdx.hidden:
--------------------------------------------------------------------------------
1 | _work in progress_
2 |
--------------------------------------------------------------------------------
/website/docs/basic-di/5.iti.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # ITI DI
6 |
7 | ```ts
8 | import { createContainer } from "iti"
9 | export interface Logger {
10 | info: (msg: string) => void
11 | warn: (msg: string) => void
12 | error: (msg: string) => void
13 | }
14 |
15 | export class ConsoleLogger implements Logger {
16 | info(msg: string): void {
17 | console.log("Console Logger: ", msg)
18 | }
19 | warn(msg: string): void {}
20 | error(msg: string): void {}
21 | }
22 |
23 | export class PinoLogger implements Logger {
24 | info(msg: string): void {
25 | console.log("Pino Logger: ", msg)
26 | }
27 | warn(msg: string): void {}
28 | error(msg: string): void {}
29 | }
30 |
31 | export class PaymentService {
32 | private readonly logger: Logger
33 | constructor(_logger: Logger) {
34 | this.logger = _logger
35 | }
36 |
37 | sendMoney() {
38 | this.logger.info("inversify logger info")
39 | return true
40 | }
41 | }
42 |
43 | const root = createContainer()
44 | .add({
45 | logger: () =>
46 | process.env.NODE_ENV === "production"
47 | ? new PinoLogger()
48 | : new ConsoleLogger(),
49 | })
50 | .add((ctx) => ({
51 | paymentService: () => new PaymentService(ctx.logger),
52 | }))
53 |
54 | const ps = root.congainers.paymentService
55 | ps.sendMoney()
56 | ```
57 |
--------------------------------------------------------------------------------
/website/docs/basic-di/6.iti-vs-pure.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 7
3 | ---
4 |
5 | import Tabs from "@theme/Tabs"
6 | import TabItem from "@theme/TabItem"
7 |
8 | # Pure DI vs ITI
9 |
10 | ## Business logic
11 |
12 | With ITI and Pure DI your business logic stays exactly the same.
13 | There is no need to add any framework specific decorators or `extends`.
14 |
15 | ```ts
16 | export interface Logger {
17 | info: (msg: string) => void
18 | warn: (msg: string) => void
19 | error: (msg: string) => void
20 | }
21 |
22 | export class ConsoleLogger implements Logger {
23 | info(msg: string): void {
24 | console.log("Console Logger: ", msg)
25 | }
26 | warn(msg: string): void {}
27 | error(msg: string): void {}
28 | }
29 |
30 | export class PinoLogger implements Logger {
31 | info(msg: string): void {
32 | console.log("Pino Logger: ", msg)
33 | }
34 | warn(msg: string): void {}
35 | error(msg: string): void {}
36 | }
37 |
38 | export class PaymentService {
39 | private readonly logger: Logger
40 | constructor(_logger: Logger) {
41 | this.logger = _logger
42 | }
43 |
44 | sendMoney() {
45 | this.logger.info("inversify logger info")
46 | return true
47 | }
48 | }
49 | ```
50 |
51 | ## Wiring
52 |
53 |
54 |
55 |
56 | ```ts title="./src/app.ts"
57 | const logger =
58 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger()
59 |
60 | const paymentService = new PaymentService(logger)
61 | paymentService.sendMoney()
62 | ```
63 |
64 |
65 |
66 |
67 | ```tsx title="./src/app.ts"
68 | const root = createContainer()
69 | .add({
70 | logger: () =>
71 | process.env.NODE_ENV === "production"
72 | ? new PinoLogger()
73 | : new ConsoleLogger(),
74 | })
75 | .add((ctx) => ({
76 | paymentService: () => new PaymentService(ctx.logger),
77 | }))
78 |
79 | const ps = root.items.paymentService
80 | ps.sendMoney()
81 | ```
82 |
83 |
84 |
85 |
86 |
87 | ## Async Request for single item in ITI
88 |
--------------------------------------------------------------------------------
/website/docs/basic-di/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Basic DI Example",
3 | "position": 30,
4 | "link": {
5 | "type": "generated-index"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/website/docs/examples/circular-dependency.ts:
--------------------------------------------------------------------------------
1 | import { createContainer } from "iti"
2 |
3 | /*
4 | // Part 1: You can create a circular dependency
5 | // in your business logic
6 | ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
7 | │ A │──▶│ B │──▶│ C │──▶│ D │──▶│ E │
8 | └───┘ └───┘ └───┘ └───┘ └───┘
9 | ▲ │
10 | │ │
11 | └───────────────┘
12 | */
13 | class A {
14 | constructor() {}
15 | }
16 | class B {
17 | constructor(a: A) {}
18 | }
19 | class C {
20 | constructor(b: B, e: E) {}
21 | }
22 | class D {
23 | constructor(c: C) {}
24 | }
25 | class E {
26 | constructor(d: E) {}
27 | }
28 |
29 | // Part 2: You can't express a circular dependency because of typescript checks
30 | createContainer()
31 | .add((ctx) => ({
32 | a: () => new A(),
33 | }))
34 | .add((ctx) => ({
35 | b: () => new B(ctx.a),
36 | }))
37 | .add((ctx) => ({
38 | // This line will throw a Typescript error at compile time
39 | c: () => new C(ctx.a, ctx.e),
40 | }))
41 |
--------------------------------------------------------------------------------
/website/docs/examples/oven-business-logic.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/examples/oven-business-logic.ts
--------------------------------------------------------------------------------
/website/docs/with-react/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "React",
3 | "position": 20,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "Working with React"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/website/docs/with-react/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Ensure Sync Containers
6 |
7 | _work in progress_
8 |
9 | ```tsx title="./src/_containers-react/EnsureEcommerce.tsx"
10 | import React, { useContext } from "react"
11 | import { generateEnsureContainerSet } from "iti-react"
12 | import { useContainerSet } from "./_editor-app-hooks"
13 |
14 | const x = generateEnsureContainerSet(() =>
15 | useContainerSet(["ecommerce", "auth"])
16 | )
17 | export const EnsureEcommerceContainer = x.EnsureWrapper
18 | export const useEcommerceContext = x.contextHook
19 | ```
20 |
21 | ```tsx title="./src/App.tsx"
22 | import { EnsureEcommerceContainer } from "./_containers-react/EnsureEcommerce"
23 |
24 | export const App = () => (
25 |
26 | Loading>}>
27 |
28 |
29 |
30 | )
31 | ```
32 |
33 | ```tsx title="./src/Currency.tsx"
34 | import { useEcommerceContext } from "../../../../_containers-react/EnsureEcommerce"
35 |
36 | export const CurrencyInfo = () => {
37 | const { currencyStore, taxStore } = useEcommerceContext().ecommerce
38 |
39 | return {currencyStore.currency}
40 | }
41 | ```
42 |
--------------------------------------------------------------------------------
/website/docs/with-react/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Full Example Configuration
6 |
7 | Basically, we create an ITI instance, add it to `React.Context` and generate fetch hooks.
8 |
9 | ```tsx title="./src/_containers/main-app"
10 | import { createContainer } from "iti"
11 |
12 | export const mainApp = () =>
13 | createContainer().add(() => ({
14 | auth: async () => {
15 | const res = await fetch("/api/profile")
16 | return {
17 | profile: res.json(),
18 | }
19 | },
20 | }))
21 | export type MainAppContainer = ReturnType
22 | ```
23 |
24 | ```tsx title="./src/_containers/hooks"
25 | import React from "react"
26 | import { getContainerSetHooks } from "iti-react"
27 | import { MainAppContainer } from "./_containers/main-app"
28 |
29 | export const MyAppContext = React.createContext({})
30 |
31 | const hooks = getContainerSetHooks(MyAppContext)
32 | export const useContainerSet = hooks.useContainerSet
33 | export const useContainer = hooks.useContainer
34 | ```
35 |
36 | ```tsx title="./src/App.tsx"
37 | import React, { useContext } from "react"
38 | import { mainApp } from "./_containers/main-app"
39 | import { MyAppContext } from "./_containers/hooks"
40 | import { Profile } from "./Profile"
41 |
42 | function App() {
43 | const itiContainerInstance = useMemo(() => mainApp(), [])
44 |
45 | return (
46 | <>
47 |
48 |
49 |
50 | >
51 | )
52 | }
53 | ```
54 |
55 | ```tsx title="./src/Profile.tsx"
56 | import { useContainer } from "./_containers/main-app"
57 |
58 | function Profile() {
59 | const [auth, authErr] = useContainer().auth
60 |
61 | if (authErr) return failed to load
62 | if (!auth) return loading...
63 |
64 | return hello {auth.profile.name}!
65 | }
66 | ```
67 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iti-docs-docosaurus",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start",
8 | "build": "docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "clear": "docusaurus clear",
12 | "serve": "docusaurus serve",
13 | "write-translations": "docusaurus write-translations",
14 | "write-heading-ids": "docusaurus write-heading-ids"
15 | },
16 | "dependencies": {
17 | "@docusaurus/core": "2.1.0",
18 | "@docusaurus/preset-classic": "2.1.0",
19 | "@mdx-js/react": "^1.6.22",
20 | "clsx": "^1.2.1",
21 | "docusaurus-lunr-search": "^2.2.0",
22 | "iti": "^0.5.0",
23 | "iti-react": "^0.5.0",
24 | "prism-react-renderer": "^1.3.5",
25 | "raw-loader": "^4.0.2",
26 | "react": "^18.2.0",
27 | "react-dom": "^18.2.0"
28 | },
29 | "devDependencies": {
30 | "@docusaurus/module-type-aliases": "2.1.0",
31 | "prettier": "^2.7.1"
32 | },
33 | "prettier": {
34 | "printWidth": 80,
35 | "semi": false,
36 | "trailingComma": "es5",
37 | "arrowParens": "always",
38 | "endOfLine": "lf"
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.5%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/website/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | - create an ordered group of docs
4 | - render a sidebar for each doc of that group
5 | - provide next/previous navigation
6 |
7 | The sidebars can be generated from the filesystem, or explicitly defined here.
8 |
9 | Create as many sidebars as you want.
10 | */
11 |
12 | // @ts-check
13 |
14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
15 | const sidebars = {
16 | // By default, Docusaurus generates a sidebar from the docs folder structure
17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
18 |
19 | // But you can create a sidebar manually
20 | /*
21 | tutorialSidebar: [
22 | {
23 | type: 'category',
24 | label: 'Tutorial',
25 | items: ['hello'],
26 | },
27 | ],
28 | */
29 | };
30 |
31 | module.exports = sidebars;
32 |
--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import clsx from "clsx"
3 | import styles from "./styles.module.css"
4 |
5 | const FeatureList = [
6 | {
7 | title: "Pure DI Upgrade",
8 | Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
9 | description: (
10 | <>
11 | You are using a DI already. Progressively upgrade and reap
12 | ITI benefits
13 | >
14 | ),
15 | },
16 | {
17 | title: "Type Safe",
18 | Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default,
19 | description: (
20 | <>
21 | If your project compiles your dependencies will resolve correctly at a
22 | runtime
23 | >
24 | ),
25 | },
26 | {
27 | title: "Asynchronous",
28 | Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default,
29 | description: <>Your app is asynchronousm so should be your framework>,
30 | },
31 | {
32 | title: "React Compatible",
33 | Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
34 | description: (
35 | <>
36 | Useful React bindings. Extract
37 | application business logic from a React hooks and components
38 | >
39 | ),
40 | },
41 | ]
42 |
43 | function Feature({ Svg, title, description }) {
44 | return (
45 |
46 | {/*
47 |
48 |
*/}
49 |
50 |
{title}
51 |
{description}
52 |
53 |
54 | )
55 | }
56 |
57 | export default function HomepageFeatures() {
58 | return (
59 |
60 | {FeatureList.map((props, idx) => (
61 |
62 | ))}
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures/styles.module.css:
--------------------------------------------------------------------------------
1 | .features {
2 | display: grid;
3 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4 | /* align-items: center; */
5 | /* justify-content: space-around; */
6 | /* flex-wrap: wrap; */
7 | gap: 16px;
8 | padding: 2rem 0;
9 | maring: 1rem;
10 | width: 100%;
11 | }
12 |
13 | .featureSvg {
14 | height: 200px;
15 | width: 200px;
16 | }
17 |
18 | .featureBlock {
19 | /* background-color: #f9f9f9;
20 | border: 1px solid #f9f9f9; */
21 | color: var(--ifm-color-info-contrast-foreground);
22 | background-color: var(--ifm-color-info-contrast-background);
23 | border: 2px solid var(--ifm-color-info-lightest);
24 | border-radius: 12px;
25 | height: 100%;
26 | padding: 24px;
27 | }
28 | /* .features div {
29 | width: 25%;
30 | } */
31 |
--------------------------------------------------------------------------------
/website/src/css/custom.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Any CSS included here will be global. The classic template
3 | * bundles Infima by default. Infima is a CSS framework designed to
4 | * work well for content-centric websites.
5 | */
6 |
7 | /* You can override the default Infima variables here. */
8 | :root {
9 | --ifm-color-primary: #2e8555;
10 | --ifm-color-primary-dark: #29784c;
11 | --ifm-color-primary-darker: #277148;
12 | --ifm-color-primary-darkest: #205d3b;
13 | --ifm-color-primary-light: #33925d;
14 | --ifm-color-primary-lighter: #359962;
15 | --ifm-color-primary-lightest: #3cad6e;
16 | --ifm-code-font-size: 80%;
17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
18 | }
19 |
20 | /* For readability concerns, you should choose a lighter palette in dark mode. */
21 | [data-theme="dark"] {
22 | --ifm-color-primary: #25c2a0;
23 | --ifm-color-primary-dark: #21af90;
24 | --ifm-color-primary-darker: #1fa588;
25 | --ifm-color-primary-darkest: #1a8870;
26 | --ifm-color-primary-light: #29d5b0;
27 | --ifm-color-primary-lighter: #32d8b4;
28 | --ifm-color-primary-lightest: #4fddbf;
29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
30 | }
31 |
--------------------------------------------------------------------------------
/website/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import clsx from "clsx"
3 | import Link from "@docusaurus/Link"
4 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
5 | import Layout from "@theme/Layout"
6 | import HomepageFeatures from "@site/src/components/HomepageFeatures"
7 |
8 | import styles from "./index.module.css"
9 |
10 | const Logo = require("@site/static/img/iti/logo.svg").default
11 |
12 | function HomepageHeader() {
13 | const { siteConfig } = useDocusaurusContext()
14 | return (
15 |
58 | )
59 | }
60 |
61 | export default function Home() {
62 | const { siteConfig } = useDocusaurusContext()
63 | return (
64 |
68 |
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/website/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | /**
2 | * CSS files with the .module.css suffix will be treated as CSS modules
3 | * and scoped locally.
4 | */
5 |
6 | .heroBanner {
7 | padding: 4rem 0;
8 | text-align: center;
9 | position: relative;
10 | overflow: hidden;
11 | }
12 |
13 | .heroLogo {
14 | width: 100%;
15 | max-width: 500px;
16 | margin: 0 auto;
17 | }
18 |
19 | .heroLogo svg {
20 | width: 30%;
21 | }
22 |
23 | .main {
24 | padding: 2rem;
25 | }
26 |
27 | @media screen and (max-width: 996px) {
28 | .heroBanner {
29 | padding: 2rem;
30 | }
31 | }
32 |
33 | .buttons {
34 | display: flex;
35 | flex-wrap: wrap;
36 | flex-direction: row;
37 | justify-content: center;
38 | gap: 1rem;
39 | }
40 |
--------------------------------------------------------------------------------
/website/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/website/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/.nojekyll
--------------------------------------------------------------------------------
/website/static/google8b69c6f2456eb7a7.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google8b69c6f2456eb7a7.html
--------------------------------------------------------------------------------
/website/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/docusaurus.png
--------------------------------------------------------------------------------
/website/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/iti/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/iti/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/static/img/iti/rastr/icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-180.png
--------------------------------------------------------------------------------
/website/static/img/iti/rastr/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-192.png
--------------------------------------------------------------------------------
/website/static/img/iti/rastr/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-512.png
--------------------------------------------------------------------------------
/website/static/img/iti/rastr/logo-2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/logo-2048.png
--------------------------------------------------------------------------------