HATEOAS-first backend framework.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
3.8 KiB

  1. import {describe, it, expect, beforeAll, afterAll} from 'vitest';
  2. import {
  3. App,
  4. app,
  5. Endpoint,
  6. endpoint,
  7. Operation,
  8. operation,
  9. statusCodes,
  10. validation as v,
  11. } from '../src/common';
  12. import {Backend, backend, DataSource, Server} from '../src/backend';
  13. import {Client} from '../src/client';
  14. import {server} from '../src/extenders/http/backend';
  15. import {client} from '../src/extenders/http/client';
  16. const op = operation({
  17. name: 'create' as const,
  18. method: 'POST' as const,
  19. });
  20. const e = endpoint({
  21. name: 'e' as const,
  22. schema: v.object({}),
  23. })
  24. .can('create');
  25. const a = app({
  26. name: 'foo' as const,
  27. })
  28. .operation(op)
  29. .endpoint(e);
  30. describe('app', () => {
  31. let theApp: App;
  32. let theBackend: Backend;
  33. let theClient: Client;
  34. let theDataSource: DataSource;
  35. let theEndpoint: Endpoint;
  36. let theServer: Server;
  37. let theOperation: Operation;
  38. beforeAll(async () => {
  39. theOperation = operation({
  40. name: 'fetch' as const,
  41. });
  42. theEndpoint = endpoint({
  43. name: 'users' as const,
  44. schema: v.object({
  45. username: v.string()
  46. }),
  47. })
  48. .param('resourceId')
  49. .can('fetch');
  50. theApp = app({
  51. name: 'foo' as const
  52. })
  53. .operation(theOperation)
  54. .endpoint(theEndpoint);
  55. theBackend = backend({
  56. app: theApp
  57. });
  58. // add recipes function that will wrap app and backend to add operations and implement them, and will return a set
  59. // of operations.
  60. //
  61. // recipes should have a backend and client counterpart.
  62. theBackend.implementOperation('fetch', async (ctx) => {
  63. // noop
  64. });
  65. theServer = server({
  66. backend: theBackend
  67. });
  68. const connectionParams = {
  69. port: 3000,
  70. };
  71. await theServer.serve(connectionParams);
  72. theClient = client({
  73. app: theApp
  74. });
  75. await theClient.connect(connectionParams);
  76. });
  77. afterAll(async () => {
  78. await theClient.disconnect();
  79. await theServer.close();
  80. });
  81. it('works', async () => {
  82. const response = await theClient
  83. .at(theEndpoint, { resourceId: 3 })
  84. .makeRequest(theOperation);
  85. expect(response).toHaveProperty('status', statusCodes.HTTP_STATUS_UNPROCESSABLE_ENTITY);
  86. });
  87. });
  88. // const theEndpoint = endpoint({
  89. // schema: v.object({
  90. // username: v.string(),
  91. // }),
  92. // })
  93. // .can('patch')
  94. // .can('query');
  95. //
  96. // const canPatch = operation({
  97. // name: 'patch' as const,
  98. // args: [
  99. // 'merge',
  100. // 'delta',
  101. // ] as const,
  102. // // TODO define resource-specific stuff, like defining URL params, etc.
  103. // });
  104. //
  105. // const canFetch = operation({
  106. // name: 'fetch' as const,
  107. // args: [
  108. // 'item',
  109. // 'default',
  110. // ] as const,
  111. // });
  112. //
  113. // const canQuery = operation({
  114. // name: 'query' as const,
  115. // });
  116. //
  117. // const canCreate = operation({
  118. // name: 'create' as const,
  119. // });
  120. //
  121. // const canEmplace = operation({
  122. // name: 'emplace' as const,
  123. // });
  124. //
  125. // const canDelete = operation({
  126. // name: 'delete' as const,
  127. // });
  128. //
  129. // export const theApp = app({
  130. // name: 'foo' as const,
  131. // })
  132. // .operation(canQuery)
  133. // .operation(canPatch)
  134. // .operation(canFetch)
  135. // .operation(canCreate)
  136. // .operation(canEmplace)
  137. // .operation(canDelete)
  138. // .endpoint(theEndpoint);
  139. // //
  140. // // const bootstrap = async (theApp: App) => {
  141. // // if (typeof window === 'undefined') {
  142. // // const { backend } = await import('./backend');
  143. // // const theBackend = backend({
  144. // // app: theApp
  145. // // });
  146. // // }
  147. // // };
  148. //
  149. // const b = backend({
  150. // app: theApp,
  151. // })
  152. // .implementOperation({
  153. // operation: 'fetch' as const,
  154. // implementation: ({
  155. // endpoint,
  156. // arg
  157. // }) => {
  158. // switch (arg) {
  159. // case 'default': {
  160. //
  161. // }
  162. // }
  163. // },
  164. // });
  165. //
  166. // const s = server({
  167. // backend: b,
  168. // })
  169. // .serve({
  170. // host: '0.0.0.0',
  171. // port: 3000,
  172. // basePath: '/api'
  173. // });