{"id":38079352,"url":"https://github.com/submodule-org/submodule","last_synced_at":"2026-01-16T20:47:17.267Z","repository":{"id":157177114,"uuid":"585473074","full_name":"submodule-org/submodule","owner":"submodule-org","description":"Submodule is a library to help building less-opinionated framework","archived":false,"fork":false,"pushed_at":"2025-01-17T09:46:49.000Z","size":2704,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-15T17:15:04.428Z","etag":null,"topics":["configuration","nodejs","reusable-components","submodule"],"latest_commit_sha":null,"homepage":"https://submodule.js.org","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/submodule-org.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-05T09:03:14.000Z","updated_at":"2025-01-17T09:46:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"fe78a055-c0c4-4eb0-a7c2-bb707b53b011","html_url":"https://github.com/submodule-org/submodule","commit_stats":null,"previous_names":["submodule-org/submodule","submodule-js/submodule"],"tags_count":97,"template":false,"template_full_name":null,"purl":"pkg:github/submodule-org/submodule","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/submodule-org%2Fsubmodule","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/submodule-org%2Fsubmodule/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/submodule-org%2Fsubmodule/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/submodule-org%2Fsubmodule/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/submodule-org","download_url":"https://codeload.github.com/submodule-org/submodule/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/submodule-org%2Fsubmodule/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28482310,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","nodejs","reusable-components","submodule"],"created_at":"2026-01-16T20:47:16.948Z","updated_at":"2026-01-16T20:47:17.201Z","avatar_url":"https://github.com/submodule-org.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [Submodule documentation](https://submodule.js.org)\n\nThe doc is out of date, soon to be updated\n\n_actualization_ in the document meant the result of a function excution, assume the function execution is expensive/or unexpected (for example, we expect limited amount of instances)\n\n_factory_ is the function that used to provide the value\n\n# Common usecases\n\n### Provide a value factory, actualize when needed. Use provide\n```typescript\nconst scope = createScope()\n\nconst valueExecutor = provide(() =\u003e /* Some value */)\nconst value = await scope.resolve(valueExecutor)\n```\nFactory can be a sync or async function. `scope#resolve` is always async ops\nWithin same the same scope, the value is cached (singleton)\n\n### Derive value\n```typescript\nconst scope = createScope()\n\nconst seed = provide(async () =\u003e /* Read from config */)\nconst hashFnExecutor = map(seed, (seed) =\u003e () =\u003e /* Do hashing */)\nconst hashFn = await scope.resolve(hashFnExecutor)\n```\n\n### Create submodule function that requires some input\n\nGiven this code\n```typescript\nmap(\n  dependencies,                     // static dependency reference\n  (dependencies) =\u003e {               // actualized static dependencies\n                                    // preparation code, reusable part, cache etc\n    return (inputParams: any[]): FinalValue =\u003e { // Runtime dependencies\n      /* Implementation */\n    }\n  }\n)\n```\n\nExample\n```typescript\nconst scope = createScope()\n\nconst seed = provide(async () =\u003e /* Read from config */)\nconst hashFnExecutor = map(seed, (seed) =\u003e (value: string) =\u003e /* Do hashing */)\nconst hashFn = await scope.resolve(hashFnExecutor)\n// hashFn('value') \u003c-- hashed value\n```\n\n### Use multiple dependencies\n\nUse combine to group multiple dependencies\n```typescript\nconst stringValue = provide(() =\u003e '1')\nconst intValue = provide(() =\u003e 2)\nconst combined: Executor\u003c{ stringValue: string, intValue: number }\u003e = combine({ stringValue, intValue })\n\nconst use = map(combined, ({ intValue, stringValue}) =\u003e { /**/ })\n//                           ^^ int\n//                                     ^^ string\n\n// shortcut, works most of the time, soemtime typescript can't resolve it\nconst use = map({ stringValue, intValue }, ({ intValue, stringValue}) =\u003e { /**/ })\n```\n\n### Group similar executors (like routes)\n```typescript\nconst stringValue1 = provide(() =\u003e '1')\nconst stringValue2 = provide(() =\u003e '2')\n\nconst stringValues: Executor\u003cstring[]\u003e = group(stringValue1, stringValue2 )\n```\n\n### Refer to the current scope inside a factory / conditional value\n\nUse the special `scoper` to access to the current scope.\n\n```typescript\nconst constantSeed = provide(() =\u003e 1)\nconst randomSeed = provide(() =\u003e Math.random())\n\nconst seed = map(\n  combine({\n    scoper,\n    constantSeed: value(constantSeed), // wrap inside value so it won't be resolved\n    randomSeed: value(randomSeed), // wrap inside value so it won't be resolved\n  }),\n  async ({ \n    scoper,\n    constantSeed,\n    randomSeed\n  }) =\u003e {\n    if (condition) return await scoper.resolve(constantSeed)\n    return await scoper.resolve(randomSeed)\n  }\n)\n```\n\nCan also use `flat` to resolve `Executor\u003cExecutor\u003cV\u003e\u003e`\n```typescript\n// In that case \nconst seed = flat(map(\n  combine({\n    constantSeed: value(constantSeed), // wrap inside value so it won't be resolved\n    randomSeed: value(randomSeed), // wrap inside value so it won't be resolved\n  }),\n  async ({ \n    constantSeed,\n    randomSeed\n  }) =\u003e condition ? constantSeed : randomSeed\n))\n```\n\nAnd `flatMap` does exactly so\n```typescript\nconst seed = flatMap(\n  combine({\n    constantSeed: value(constantSeed), // wrap inside value so it won't be resolved\n    randomSeed: value(randomSeed), // wrap inside value so it won't be resolved\n  }),\n  async ({ \n    constantSeed,\n    randomSeed\n  }) =\u003e condition ? constantSeed : randomSeed\n)\n```\n\n# Testing\nMain purpose of submodule is to make the code\n- side-effect free (app'll be faster to start)\n- testing made easy\n- testing should be fast, and easy, mock free and can run in parallel\n\nScope is the key in this testing technique\n\nThere are certain common testing techniques\n\n### Assume value in the change to simluate different testing situations\n\nFor example\n```typescript\nfunction tomorrow() {\n  /** implementation */\n}\n```\nThis function is likely rely on `new Date()` to implement. This is a global object and by mocking the global object, the test will not be able to run in parallel and depending on test framework mocking to be able to test.\n\nRewrite the code into\n```typescript\nconst newDateFn = value(() =\u003e new Date())\nconst tomorrowFn = map(newDateFn, (newDateFn) =\u003e {\n  /** Implementation **/\n})\n```\nThe implementation is mostly the same, now how to test?\n\n```typescript\n// use ResolveValue to force value of dependency\n\nconst scope = createScope()\nscope.resolveValue(newDateFn, value(() =\u003e /* mock date*/))\n\nconst r = await scope.resolve(torrowFn)\nr() // \u003c-- day after the mock date\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubmodule-org%2Fsubmodule","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubmodule-org%2Fsubmodule","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubmodule-org%2Fsubmodule/lists"}