sonamu 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/api/context.d.ts +4 -1
  2. package/dist/api/context.d.ts.map +1 -1
  3. package/dist/api/decorators.d.ts.map +1 -1
  4. package/dist/api/decorators.js +1 -1
  5. package/dist/api/decorators.js.map +1 -1
  6. package/dist/api/sonamu.d.ts +7 -6
  7. package/dist/api/sonamu.d.ts.map +1 -1
  8. package/dist/api/sonamu.js +1 -1
  9. package/dist/api/sonamu.js.map +1 -1
  10. package/dist/bin/build-config.d.ts +4 -0
  11. package/dist/bin/build-config.d.ts.map +1 -1
  12. package/dist/bin/build-config.js +1 -1
  13. package/dist/bin/build-config.js.map +1 -1
  14. package/dist/bin/cli-wrapper.js +1 -1
  15. package/dist/bin/cli-wrapper.js.map +1 -1
  16. package/dist/bin/cli.js +1 -1
  17. package/dist/bin/cli.js.map +1 -1
  18. package/dist/database/base-model.d.ts +8 -1
  19. package/dist/database/base-model.d.ts.map +1 -1
  20. package/dist/database/base-model.js +1 -1
  21. package/dist/database/base-model.js.map +1 -1
  22. package/dist/database/db.js +1 -1
  23. package/dist/database/db.js.map +1 -1
  24. package/dist/file-storage/driver.d.ts +3 -0
  25. package/dist/file-storage/driver.d.ts.map +1 -1
  26. package/dist/file-storage/driver.js +1 -1
  27. package/dist/file-storage/driver.js.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/stream/index.d.ts +2 -0
  33. package/dist/stream/index.d.ts.map +1 -0
  34. package/dist/stream/index.js +2 -0
  35. package/dist/stream/index.js.map +1 -0
  36. package/dist/stream/sse.d.ts +13 -0
  37. package/dist/stream/sse.d.ts.map +1 -0
  38. package/dist/stream/sse.js +2 -0
  39. package/dist/stream/sse.js.map +1 -0
  40. package/dist/types/types.d.ts +5 -3
  41. package/dist/types/types.d.ts.map +1 -1
  42. package/dist/types/types.js.map +1 -1
  43. package/package.json +6 -4
  44. package/src/api/context.ts +10 -5
  45. package/src/api/decorators.ts +1 -3
  46. package/src/api/sonamu.ts +60 -26
  47. package/src/bin/build-config.ts +5 -0
  48. package/src/bin/cli-wrapper.ts +20 -6
  49. package/src/bin/cli.ts +16 -21
  50. package/src/database/base-model.ts +29 -3
  51. package/src/database/db.ts +1 -1
  52. package/src/file-storage/driver.ts +10 -0
  53. package/src/index.ts +1 -0
  54. package/src/stream/index.ts +1 -0
  55. package/src/stream/sse.ts +49 -0
  56. package/src/types/types.ts +10 -4
  57. package/tsconfig.json +4 -0
  58. package/dist/api/sonamu.types.d.ts +0 -30
  59. package/dist/api/sonamu.types.d.ts.map +0 -1
  60. package/dist/api/sonamu.types.js +0 -2
  61. package/dist/api/sonamu.types.js.map +0 -1
  62. package/dist/base-model-CEB0H0aO.d.mts +0 -43
  63. package/dist/base-model-CrqDMYhI.d.ts +0 -43
  64. package/dist/bin/cli-wrapper.d.mts +0 -1
  65. package/dist/bin/cli-wrapper.mjs +0 -43
  66. package/dist/bin/cli-wrapper.mjs.map +0 -1
  67. package/dist/bin/cli.d.mts +0 -2
  68. package/dist/bin/cli.mjs +0 -907
  69. package/dist/bin/cli.mjs.map +0 -1
  70. package/dist/chunk-2WAC2GER.js +0 -7625
  71. package/dist/chunk-2WAC2GER.js.map +0 -1
  72. package/dist/chunk-C3IPIF6O.mjs +0 -1581
  73. package/dist/chunk-C3IPIF6O.mjs.map +0 -1
  74. package/dist/chunk-EXHKSVTE.js +0 -280
  75. package/dist/chunk-EXHKSVTE.js.map +0 -1
  76. package/dist/chunk-FCERKIIF.mjs +0 -7623
  77. package/dist/chunk-FCERKIIF.mjs.map +0 -1
  78. package/dist/chunk-HGIBJYOU.mjs +0 -231
  79. package/dist/chunk-HGIBJYOU.mjs.map +0 -1
  80. package/dist/chunk-JKSOJRQA.mjs +0 -280
  81. package/dist/chunk-JKSOJRQA.mjs.map +0 -1
  82. package/dist/chunk-OTKKFP3Y.js +0 -1581
  83. package/dist/chunk-OTKKFP3Y.js.map +0 -1
  84. package/dist/chunk-PTFDTOJU.mjs +0 -19
  85. package/dist/chunk-PTFDTOJU.mjs.map +0 -1
  86. package/dist/chunk-UZ2IY5VE.js +0 -231
  87. package/dist/chunk-UZ2IY5VE.js.map +0 -1
  88. package/dist/database/drivers/knex/base-model.d.mts +0 -16
  89. package/dist/database/drivers/knex/base-model.d.ts +0 -16
  90. package/dist/database/drivers/knex/base-model.js +0 -55
  91. package/dist/database/drivers/knex/base-model.js.map +0 -1
  92. package/dist/database/drivers/knex/base-model.mjs +0 -56
  93. package/dist/database/drivers/knex/base-model.mjs.map +0 -1
  94. package/dist/database/drivers/kysely/base-model.d.mts +0 -22
  95. package/dist/database/drivers/kysely/base-model.d.ts +0 -22
  96. package/dist/database/drivers/kysely/base-model.js +0 -64
  97. package/dist/database/drivers/kysely/base-model.js.map +0 -1
  98. package/dist/database/drivers/kysely/base-model.mjs +0 -65
  99. package/dist/database/drivers/kysely/base-model.mjs.map +0 -1
  100. package/dist/database/types.d.ts +0 -39
  101. package/dist/database/types.d.ts.map +0 -1
  102. package/dist/database/types.js +0 -2
  103. package/dist/database/types.js.map +0 -1
  104. package/dist/index.d.mts +0 -813
  105. package/dist/index.mjs +0 -435
  106. package/dist/index.mjs.map +0 -1
  107. package/dist/model-aFgomcdc.d.mts +0 -1112
  108. package/dist/model-aFgomcdc.d.ts +0 -1112
  109. package/dist/smd/smd-manager.d.ts +0 -28
  110. package/dist/smd/smd-manager.d.ts.map +0 -1
  111. package/dist/smd/smd-manager.js +0 -2
  112. package/dist/smd/smd-manager.js.map +0 -1
  113. package/dist/smd/smd.d.ts +0 -40
  114. package/dist/smd/smd.d.ts.map +0 -1
  115. package/dist/smd/smd.js +0 -2
  116. package/dist/smd/smd.js.map +0 -1
package/src/api/sonamu.ts CHANGED
@@ -1,18 +1,24 @@
1
- import path from "path";
2
- import { readFile } from "fs/promises";
3
- import { exists } from "../utils/fs-utils";
4
1
  import { AsyncLocalStorage } from "async_hooks";
5
2
  import chalk from "chalk";
6
3
  import fastify from "fastify";
4
+ import { readFile } from "fs/promises";
5
+ import path from "path";
6
+ import { exists } from "../utils/fs-utils";
7
7
 
8
- import { ZodError } from "zod";
9
- import { getZodObjectFromApi } from "./code-converters";
8
+ import type { FSWatcher } from "chokidar";
9
+ import { formatInTimeZone } from "date-fns-tz";
10
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
11
+ import type { IncomingMessage, Server, ServerResponse } from "http";
12
+ import { ZodError, ZodObject } from "zod";
13
+ import { DB, SonamuDBConfig } from "../database/db";
14
+ import { attachOnDuplicateUpdate } from "../database/knex-plugins/knex-on-duplicate-update";
10
15
  import {
11
16
  BadRequestException,
12
17
  NotFoundException,
13
18
  } from "../exceptions/so-exceptions";
14
- import { humanizeZodError } from "../utils/zod-error";
15
- import { fastifyCaster } from "./caster";
19
+ import type { Driver } from "../file-storage/driver";
20
+ import { createSSEFactory } from "../stream/sse";
21
+ import type { Syncer } from "../syncer/syncer";
16
22
  import {
17
23
  ApiParamType,
18
24
  SonamuFastifyConfig,
@@ -20,16 +26,11 @@ import {
20
26
  } from "../types/types";
21
27
  import { isLocal, isTest } from "../utils/controller";
22
28
  import { findApiRootPath } from "../utils/utils";
23
- import { DB, SonamuDBConfig } from "../database/db";
24
- import { attachOnDuplicateUpdate } from "../database/knex-plugins/knex-on-duplicate-update";
25
- import type { ExtendedApi } from "./decorators";
26
- import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
27
- import type { IncomingMessage, Server, ServerResponse } from "http";
29
+ import { humanizeZodError } from "../utils/zod-error";
30
+ import { fastifyCaster } from "./caster";
31
+ import { getZodObjectFromApi } from "./code-converters";
28
32
  import type { Context, UploadContext } from "./context";
29
- import type { Syncer } from "../syncer/syncer";
30
- import type { FSWatcher } from "chokidar";
31
- import { formatInTimeZone } from "date-fns-tz";
32
- import type { Driver } from "../file-storage/driver";
33
+ import type { ExtendedApi } from "./decorators";
33
34
 
34
35
  export type SonamuConfig = {
35
36
  projectName?: string;
@@ -389,16 +390,31 @@ class SonamuClass {
389
390
  return cachedData;
390
391
  }
391
392
 
392
- // 결과 (AsyncLocalStorage 적용)
393
- const context = config.contextProvider(
394
- {
395
- request,
396
- reply,
397
- headers: request.headers,
398
- },
393
+ // createSSEFactory 함수에 미리 request의 socket과 reply를 바인딩.
394
+ const createSSE = (<T extends ZodObject>(
395
+ _request: FastifyRequest,
396
+ _reply: FastifyReply,
397
+ _events: T
398
+ ) => createSSEFactory(_request.socket, _reply, _events)).bind(
399
+ null,
399
400
  request,
400
401
  reply
401
402
  );
403
+
404
+ // 결과 (AsyncLocalStorage 적용)
405
+ const context: Context = {
406
+ ...config.contextProvider(
407
+ {
408
+ request,
409
+ reply,
410
+ headers: request.headers,
411
+ createSSE,
412
+ },
413
+ request,
414
+ reply
415
+ ),
416
+ };
417
+
402
418
  const model = this.syncer.models[api.modelName];
403
419
  return this.asyncLocalStorage.run({ context }, async () => {
404
420
  const result = await (model as any)[api.methodName].apply(
@@ -440,10 +456,26 @@ class SonamuClass {
440
456
  return;
441
457
  }
442
458
 
443
- await this.handleFileChange(event, filePath);
459
+ try {
460
+ await this.handleFileChange(event, filePath);
461
+ } catch (e) {
462
+ console.error(e);
463
+ }
444
464
  });
445
465
  }
446
466
 
467
+ /*
468
+ A function that automatically handles init and destroy when using Sonamu via scripts.
469
+ */
470
+ async runScript(fn: () => Promise<void>) {
471
+ await this.init(true, false, undefined, false);
472
+ try {
473
+ await fn();
474
+ } finally {
475
+ await this.destroy();
476
+ }
477
+ }
478
+
447
479
  private registerPlugins(
448
480
  server: FastifyInstance,
449
481
  plugins: SonamuServerOptions["plugins"]
@@ -453,10 +485,11 @@ class SonamuClass {
453
485
  }
454
486
 
455
487
  const pluginsModules = {
456
- formbody: "@fastify/formbody",
457
- qs: "fastify-qs",
458
488
  cors: "@fastify/cors",
489
+ formbody: "@fastify/formbody",
459
490
  multipart: "@fastify/multipart",
491
+ qs: "fastify-qs",
492
+ sse: "fastify-sse-v2",
460
493
  } as const;
461
494
 
462
495
  const registerPlugin = <K extends keyof NonNullable<typeof plugins>>(
@@ -561,6 +594,7 @@ class SonamuClass {
561
594
  const { BaseModel } = require("../database/base-model");
562
595
  await BaseModel.destroy();
563
596
  await this.watcher?.close();
597
+ this.storage?.destroy();
564
598
  }
565
599
  }
566
600
  export const Sonamu = new SonamuClass();
@@ -3,3 +3,8 @@
3
3
  */
4
4
  export const SWC_BUILD_COMMAND =
5
5
  "swc src -d dist --strip-leading-paths --source-maps -C module.type=commonjs -C jsc.parser.syntax=typescript -C jsc.parser.decorators=true -C jsc.target=es5";
6
+
7
+ /**
8
+ * TSC 타입 체크 명령어
9
+ */
10
+ export const TSC_TYPE_CHECK_COMMAND = "tsc --noEmit";
@@ -4,24 +4,34 @@ import { spawnSync, execSync } from "child_process";
4
4
  import { resolve } from "path";
5
5
  import { existsSync } from "fs";
6
6
  import chalk from "chalk";
7
- import { SWC_BUILD_COMMAND } from "./build-config";
7
+ import { SWC_BUILD_COMMAND, TSC_TYPE_CHECK_COMMAND } from "./build-config";
8
8
 
9
9
  const scriptPath = resolve(__dirname, "cli.js");
10
10
  const args = process.argv.slice(2);
11
11
 
12
12
  // build 명령어는 dist 없이도 실행 가능하도록 cli.ts 외부에서 처리(Sonamu.init에서 dist 필요)
13
- function build() {
13
+ function build(checkTypes: boolean = false) {
14
14
  try {
15
15
  execSync(SWC_BUILD_COMMAND, { cwd: process.cwd(), stdio: "inherit" });
16
16
  } catch (error) {
17
17
  console.error(chalk.red("Build failed."), error);
18
18
  process.exit(1);
19
19
  }
20
+
21
+ if (checkTypes) {
22
+ try {
23
+ console.log(chalk.blue("Checking types with tsc..."));
24
+ execSync(TSC_TYPE_CHECK_COMMAND, { cwd: process.cwd(), stdio: "inherit" });
25
+ } catch (error) {
26
+ console.error(chalk.red("Type check failed."), error);
27
+ process.exit(1);
28
+ }
29
+ }
20
30
  }
21
31
 
22
32
  if (args[0] === "build") {
23
33
  console.log(chalk.blue("Building the project..."));
24
- build();
34
+ build(true);
25
35
  console.log(chalk.green("Build completed successfully."));
26
36
  process.exit(0);
27
37
  }
@@ -35,8 +45,12 @@ if (!existsSync(scriptPath)) {
35
45
  process.exit(1);
36
46
  }
37
47
 
38
- const result = spawnSync(process.execPath, [scriptPath, ...args], {
39
- stdio: "inherit",
40
- });
48
+ const result = spawnSync(
49
+ process.execPath,
50
+ ["--no-warnings", scriptPath, ...args],
51
+ {
52
+ stdio: "inherit",
53
+ }
54
+ );
41
55
 
42
56
  process.exit(result.status ?? 1);
package/src/bin/cli.ts CHANGED
@@ -1,27 +1,29 @@
1
- /* Global Begin */
2
1
  import chalk from "chalk";
3
- console.log(chalk.bgBlue(`BEGIN ${new Date()}`));
4
-
5
2
  import dotenv from "dotenv";
6
3
  dotenv.config();
7
4
 
8
5
  import path from "path";
9
6
  import { tsicli } from "tsicli";
10
7
  import { execSync } from "child_process";
11
- import { mkdir, readFile, readdir, writeFile } from "fs/promises";
8
+ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
12
9
  import { exists } from "../utils/fs-utils";
13
10
  import process from "process";
14
11
  import { Sonamu } from "../api";
15
12
  import knex, { Knex } from "knex";
13
+ import { findApiRootPath } from "../utils/utils";
16
14
  import { EntityManager } from "../entity/entity-manager";
17
15
  import { Migrator } from "../migration/migrator";
18
16
  import { FixtureManager } from "../testing/fixture-manager";
19
- import { SWC_BUILD_COMMAND } from "./build-config";
17
+ // import { SWC_BUILD_COMMAND } from "./build-config";
18
+ import { NodemonSettings } from "nodemon";
20
19
 
21
20
  let migrator: Migrator;
22
21
 
23
22
  async function bootstrap() {
24
- await Sonamu.init(false, false);
23
+ // dev:serve 명령어가 아닌 경우에만 Sonamu 초기화
24
+ if (process.argv[2] !== "dev:serve") {
25
+ await Sonamu.init(false, false);
26
+ }
25
27
 
26
28
  await tsicli(process.argv, {
27
29
  types: {
@@ -84,16 +86,13 @@ bootstrap().finally(async () => {
84
86
  await migrator.destroy();
85
87
  }
86
88
  await FixtureManager.destroy();
87
-
88
- /* Global End */
89
- console.log(chalk.bgBlue(`END ${new Date()}\n`));
90
89
  });
91
90
 
92
91
  async function dev_serve() {
93
92
  const nodemon = await import("nodemon");
94
93
 
95
94
  const nodemonConfig = await (async () => {
96
- const projectNodemonPath = path.join(Sonamu.apiRootPath, "nodemon.json");
95
+ const projectNodemonPath = path.join(findApiRootPath(), "nodemon.json");
97
96
  const hasProjectNodemon = await exists(projectNodemonPath);
98
97
 
99
98
  if (hasProjectNodemon) {
@@ -104,12 +103,11 @@ async function dev_serve() {
104
103
  watch: ["src/index.ts"],
105
104
  ignore: ["dist/**", "**/*.js", "**/*.d.ts"],
106
105
  exec: [
107
- SWC_BUILD_COMMAND,
108
- "node -r source-map-support/register -r dotenv/config dist/index.js",
106
+ // SWC_BUILD_COMMAND,
107
+ "node --no-warnings -r source-map-support/register -r dotenv/config dist/index.js",
109
108
  ].join(" && "),
110
- };
109
+ } as NodemonSettings;
111
110
  })();
112
-
113
111
  nodemon.default(nodemonConfig);
114
112
 
115
113
  // 프로세스 종료 처리
@@ -334,25 +332,22 @@ async function stub_practice(name: string) {
334
332
  const fileName = `p${currentSeqNo}-${name}.ts`;
335
333
  const dstPath = path.join(practiceDir, fileName);
336
334
 
337
- // FIXME
338
335
  const code = [
339
- `import { BaseModel } from "sonamu";`,
336
+ `import { Sonamu } from "sonamu";`,
340
337
  "",
341
338
  `console.clear();`,
342
339
  `console.log("${fileName}");`,
343
340
  "",
344
- `async function bootstrap() {`,
341
+ `Sonamu.runScript(async () => {`,
345
342
  ` // TODO`,
346
- `}`,
347
- `bootstrap().finally(async () => {`,
348
- `await BaseModel.destroy();`,
349
343
  `});`,
344
+ "",
350
345
  ].join("\n");
351
346
  await writeFile(dstPath, code);
352
347
 
353
348
  execSync(`code ${dstPath}`);
354
349
 
355
- const runCode = `yarn node -r source-map-support/register dist/practices/${fileName.replace(
350
+ const runCode = `yarn node -r dotenv/config -r source-map-support/register dist/practices/${fileName.replace(
356
351
  ".ts",
357
352
  ".js"
358
353
  )}`;
@@ -218,6 +218,7 @@ export class BaseModelClass {
218
218
  subset,
219
219
  subsetQuery,
220
220
  build,
221
+ afterBuild,
221
222
  debug,
222
223
  db: _db,
223
224
  optimizeCountQuery,
@@ -232,6 +233,13 @@ export class BaseModelClass {
232
233
  joins: SubsetQuery["joins"];
233
234
  virtual: string[];
234
235
  }) => Knex.QueryBuilder;
236
+ afterBuild?: (buildParams: {
237
+ qb: Knex.QueryBuilder;
238
+ db: Knex;
239
+ select: (string | Knex.Raw)[];
240
+ joins: SubsetQuery["joins"];
241
+ virtual: string[];
242
+ }) => Knex.QueryBuilder;
235
243
  baseTable?: string;
236
244
  debug?: boolean | "list" | "count";
237
245
  db?: Knex;
@@ -303,7 +311,16 @@ export class BaseModelClass {
303
311
  applyJoinClause(clonedQb, joins);
304
312
  }
305
313
 
306
- const parsedQuery = parser.astify(clonedQb.toQuery());
314
+ const processedQb =
315
+ afterBuild?.({
316
+ qb: clonedQb,
317
+ db,
318
+ select,
319
+ joins,
320
+ virtual,
321
+ }) ?? clonedQb;
322
+
323
+ const parsedQuery = parser.astify(processedQb.toQuery());
307
324
  const q = Array.isArray(parsedQuery) ? parsedQuery[0] : parsedQuery;
308
325
  if (q.type !== "select") {
309
326
  throw new Error("Invalid query");
@@ -346,10 +363,19 @@ export class BaseModelClass {
346
363
  }
347
364
 
348
365
  // select, rows
349
- const listQuery = qb.clone().select(select);
366
+ const clonedQb = qb.clone().select(select);
350
367
 
351
368
  // join
352
- applyJoinClause(listQuery, joins);
369
+ applyJoinClause(clonedQb, joins);
370
+
371
+ const listQuery =
372
+ afterBuild?.({
373
+ qb: clonedQb,
374
+ db,
375
+ select,
376
+ joins,
377
+ virtual,
378
+ }) ?? clonedQb;
353
379
 
354
380
  let rows = await listQuery;
355
381
  // debug: listQuery
@@ -163,8 +163,8 @@ class DBClass {
163
163
  );
164
164
  const fixture_remote = _.merge({}, defaultKnexConfig, devMasterOptions, {
165
165
  connection: {
166
+ // NOTE: fixture remote는 default connection의 DB를 override해선 안됨.
166
167
  database: `${config.database}_fixture_remote`,
167
- ...config.defaultOptions?.connection,
168
168
  },
169
169
  });
170
170
 
@@ -24,6 +24,8 @@ export interface Driver {
24
24
  getUrl(key: string): string;
25
25
 
26
26
  getSignedUrl(key: string, expiresIn?: number): Promise<string>;
27
+
28
+ destroy(): void;
27
29
  }
28
30
 
29
31
  export type FSDriverConfig = {
@@ -58,6 +60,10 @@ export class FSDriver implements Driver {
58
60
  async getSignedUrl(key: string, _expiresIn?: number): Promise<string> {
59
61
  return this.getUrl(key);
60
62
  }
63
+
64
+ destroy() {
65
+ // 아무것도 하지 않음
66
+ }
61
67
  }
62
68
 
63
69
  export type S3DriverConfig = S3ClientConfig & {
@@ -118,4 +124,8 @@ export class S3Driver implements Driver {
118
124
 
119
125
  return visibility;
120
126
  }
127
+
128
+ destroy() {
129
+ this.s3.destroy();
130
+ }
121
131
  }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./database/puri-wrapper";
10
10
  export * from "./database/puri.types";
11
11
  export * from "./exceptions/error-handler";
12
12
  export * from "./exceptions/so-exceptions";
13
+ export * from "./stream/sse";
13
14
  export * from "./types/types";
14
15
  export * from "./utils/controller";
15
16
  export * from "./utils/model";
@@ -0,0 +1 @@
1
+ export * from "./sse";
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import type { FastifyRequest, FastifyReply } from "fastify";
3
+
4
+ // NOTE(Haze, 251106): context provider에서 인자를 채워주면 createSSE(events)만으로 사용 가능
5
+ export function createSSEFactory<T extends z.ZodObject>(
6
+ socket: FastifyRequest["socket"],
7
+ reply: FastifyReply,
8
+ _events: T
9
+ ) {
10
+ return new SSEConnection<T>(socket, reply);
11
+ }
12
+
13
+ class SSEConnection<T extends z.ZodObject> {
14
+ private _closed = false;
15
+
16
+ constructor(
17
+ private readonly socket: FastifyRequest["socket"],
18
+ private readonly reply: FastifyReply
19
+ ) {
20
+ this.socket.on("close", () => {
21
+ this._closed = true;
22
+ });
23
+ }
24
+
25
+ publish<K extends keyof z.infer<T>>(event: K, data: z.infer<T>[K]): void {
26
+ if (this._closed) {
27
+ return;
28
+ }
29
+
30
+ this.reply.sse({
31
+ event: event as string,
32
+ data: JSON.stringify(data),
33
+ });
34
+ }
35
+
36
+ async end(): Promise<void> {
37
+ if (this._closed) {
38
+ return;
39
+ }
40
+
41
+ this.reply.sse({
42
+ event: "end",
43
+ data: "END",
44
+ });
45
+
46
+ await new Promise((resolve) => setTimeout(resolve, 200));
47
+ this.reply.raw.end();
48
+ }
49
+ }
@@ -12,6 +12,7 @@ import { z } from "zod";
12
12
  import type { Context, ApiDecoratorOptions } from "../api";
13
13
  import type { FastifyMultipartOptions } from "@fastify/multipart";
14
14
  import type { Driver } from "../file-storage/driver";
15
+ import type { SsePluginOptions } from "fastify-sse-v2/lib/types";
15
16
 
16
17
  /*
17
18
  Enums
@@ -822,7 +823,7 @@ export type RelationNode = {
822
823
  export interface DatabaseSchemaExtend {}
823
824
  export type ManyToManyBaseSchema<
824
825
  FromIdKey extends string,
825
- ToIdKey extends string
826
+ ToIdKey extends string,
826
827
  > = {
827
828
  id: number;
828
829
  } & {
@@ -833,7 +834,10 @@ export type ManyToManyBaseSchema<
833
834
 
834
835
  export type SonamuFastifyConfig = {
835
836
  contextProvider: (
836
- defaultContext: Pick<Context, "request" | "reply" | "headers">,
837
+ defaultContext: Pick<
838
+ Context,
839
+ "request" | "reply" | "headers" | "createSSE"
840
+ >,
837
841
  request: FastifyRequest,
838
842
  reply: FastifyReply
839
843
  ) => Context;
@@ -879,10 +883,12 @@ export type SonamuServerOptions = {
879
883
  };
880
884
 
881
885
  plugins?: {
882
- formbody?: boolean | FastifyFormbodyOptions;
883
- qs?: boolean | QsPluginOptions;
884
886
  cors?: boolean | FastifyCorsOptions;
887
+ formbody?: boolean | FastifyFormbodyOptions;
885
888
  multipart?: boolean | FastifyMultipartOptions;
889
+ qs?: boolean | QsPluginOptions;
890
+ sse?: boolean | SsePluginOptions;
891
+
886
892
  custom?: (server: FastifyInstance) => void;
887
893
  };
888
894
 
package/tsconfig.json CHANGED
@@ -6,6 +6,10 @@
6
6
  "outDir": "dist",
7
7
  "sourceMap": true,
8
8
  "lib": ["esnext", "dom"],
9
+
10
+ // NOTE(Haze, 251106): SSE 관련 fastify 타입 이슈로 명시적으로 추가함.
11
+ "types": ["fastify-sse-v2"],
12
+
9
13
  "declaration": true,
10
14
  "declarationMap": true,
11
15
 
@@ -1,30 +0,0 @@
1
- import type { FastifyReply, FastifyRequest } from "fastify";
2
- import type { Context } from "./context";
3
- import type { ApiParamType, ApiParam } from "../types/types";
4
- import type { ApiDecoratorOptions } from "./decorators";
5
- export type SonamuFastifyConfig = {
6
- contextProvider: (defaultContext: Pick<Context, "headers" | "reply">, request: FastifyRequest, reply: FastifyReply) => Context;
7
- guardHandler: (guard: string, request: FastifyRequest, api: {
8
- typeParameters: ApiParamType.TypeParam[];
9
- parameters: ApiParam[];
10
- returnType: ApiParamType;
11
- modelName: string;
12
- methodName: string;
13
- path: string;
14
- options: ApiDecoratorOptions;
15
- }) => void;
16
- cache?: {
17
- get: (key: string) => Promise<unknown | null>;
18
- put: (key: string, value: unknown, ttl?: number) => Promise<void>;
19
- resolveKey: (path: string, reqBody: {
20
- [key: string]: unknown;
21
- }) => {
22
- cache: false;
23
- } | {
24
- cache: true;
25
- key: string;
26
- ttl?: number;
27
- };
28
- };
29
- };
30
- //# sourceMappingURL=sonamu.types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sonamu.types.d.ts","sourceRoot":"","sources":["../../src/api/sonamu.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,EAAE,CACf,cAAc,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,EAClD,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,KAChB,OAAO,CAAC;IACb,YAAY,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE;QACH,cAAc,EAAE,YAAY,CAAC,SAAS,EAAE,CAAC;QACzC,UAAU,EAAE,QAAQ,EAAE,CAAC;QACvB,UAAU,EAAE,YAAY,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,mBAAmB,CAAC;KAC9B,KACE,IAAI,CAAC;IACV,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QAC9C,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAClE,UAAU,EAAE,CACV,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;YACP,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,KAEC;YACE,KAAK,EAAE,KAAK,CAAC;SACd,GACD;YACE,KAAK,EAAE,IAAI,CAAC;YACZ,GAAG,EAAE,MAAM,CAAC;YACZ,GAAG,CAAC,EAAE,MAAM,CAAC;SACd,CAAC;KACP,CAAC;CACH,CAAC"}
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});
2
- //# sourceMappingURL=sonamu.types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/api/sonamu.types.ts"],"names":[],"mappings":""}
@@ -1,43 +0,0 @@
1
- import { c as DatabaseDriver, d as DriverSpec, S as SubsetQuery, D as DBPreset, B as BaseListParams, K as KnexClient, b as KyselyClient } from './model-aFgomcdc.mjs';
2
-
3
- declare abstract class BaseModelClassAbstract<D extends DatabaseDriver> {
4
- modelName: string;
5
- protected abstract applyJoins(qb: DriverSpec[D]["adapter"], joins: SubsetQuery["joins"]): DriverSpec[D]["adapter"];
6
- protected abstract executeCountQuery(qb: DriverSpec[D]["queryBuilder"]): Promise<number>;
7
- abstract getDB(which: DBPreset): DriverSpec[D]["core"];
8
- abstract destroy(): Promise<void>;
9
- runSubsetQuery<T extends BaseListParams, U extends string>({ params, baseTable, subset, subsetQuery, build, debug, db: _db, optimizeCountQuery, }: {
10
- subset: U;
11
- params: T;
12
- subsetQuery: SubsetQuery;
13
- build: (buildParams: {
14
- qb: DriverSpec[D]["queryBuilder"];
15
- db: DriverSpec[D]["core"];
16
- select: SubsetQuery["select"];
17
- joins: SubsetQuery["joins"];
18
- virtual: string[];
19
- }) => DriverSpec[D]["queryBuilder"];
20
- baseTable?: DriverSpec[D]["table"];
21
- debug?: boolean | "list" | "count";
22
- db?: DriverSpec[D]["core"];
23
- optimizeCountQuery?: boolean;
24
- }): Promise<{
25
- rows: any[];
26
- total?: number;
27
- subsetQuery: SubsetQuery;
28
- qb: DriverSpec[D]["queryBuilder"];
29
- }>;
30
- useLoaders(db: DriverSpec[D]["adapter"], rows: any[], loaders: SubsetQuery["loaders"]): Promise<any[]>;
31
- protected buildHasManyQuery(db: DriverSpec[D]["adapter"], loader: SubsetQuery["loaders"][number], fromIds: any[]): Promise<{
32
- subQ: KnexClient | KyselyClient;
33
- col: string;
34
- }>;
35
- protected buildManyToManyQuery(db: DriverSpec[D]["adapter"], loader: SubsetQuery["loaders"][number], fromIds: any[]): Promise<{
36
- subQ: KnexClient | KyselyClient;
37
- col: string;
38
- }>;
39
- myNow(timestamp?: number): string;
40
- hydrate<T>(rows: T[]): T[];
41
- }
42
-
43
- export { BaseModelClassAbstract as B };
@@ -1,43 +0,0 @@
1
- import { c as DatabaseDriver, d as DriverSpec, S as SubsetQuery, D as DBPreset, B as BaseListParams, K as KnexClient, b as KyselyClient } from './model-aFgomcdc.js';
2
-
3
- declare abstract class BaseModelClassAbstract<D extends DatabaseDriver> {
4
- modelName: string;
5
- protected abstract applyJoins(qb: DriverSpec[D]["adapter"], joins: SubsetQuery["joins"]): DriverSpec[D]["adapter"];
6
- protected abstract executeCountQuery(qb: DriverSpec[D]["queryBuilder"]): Promise<number>;
7
- abstract getDB(which: DBPreset): DriverSpec[D]["core"];
8
- abstract destroy(): Promise<void>;
9
- runSubsetQuery<T extends BaseListParams, U extends string>({ params, baseTable, subset, subsetQuery, build, debug, db: _db, optimizeCountQuery, }: {
10
- subset: U;
11
- params: T;
12
- subsetQuery: SubsetQuery;
13
- build: (buildParams: {
14
- qb: DriverSpec[D]["queryBuilder"];
15
- db: DriverSpec[D]["core"];
16
- select: SubsetQuery["select"];
17
- joins: SubsetQuery["joins"];
18
- virtual: string[];
19
- }) => DriverSpec[D]["queryBuilder"];
20
- baseTable?: DriverSpec[D]["table"];
21
- debug?: boolean | "list" | "count";
22
- db?: DriverSpec[D]["core"];
23
- optimizeCountQuery?: boolean;
24
- }): Promise<{
25
- rows: any[];
26
- total?: number;
27
- subsetQuery: SubsetQuery;
28
- qb: DriverSpec[D]["queryBuilder"];
29
- }>;
30
- useLoaders(db: DriverSpec[D]["adapter"], rows: any[], loaders: SubsetQuery["loaders"]): Promise<any[]>;
31
- protected buildHasManyQuery(db: DriverSpec[D]["adapter"], loader: SubsetQuery["loaders"][number], fromIds: any[]): Promise<{
32
- subQ: KnexClient | KyselyClient;
33
- col: string;
34
- }>;
35
- protected buildManyToManyQuery(db: DriverSpec[D]["adapter"], loader: SubsetQuery["loaders"][number], fromIds: any[]): Promise<{
36
- subQ: KnexClient | KyselyClient;
37
- col: string;
38
- }>;
39
- myNow(timestamp?: number): string;
40
- hydrate<T>(rows: T[]): T[];
41
- }
42
-
43
- export { BaseModelClassAbstract as B };
@@ -1 +0,0 @@
1
- #!/usr/bin/env ts-node