create-nexwire 4.2.1 → 4.2.3

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 (54) hide show
  1. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  2. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  3. package/express/src/framework/database/app-schema.ts +37 -0
  4. package/express/src/framework/database/connection.ts +198 -196
  5. package/express/src/framework/database/seed.ts +35 -22
  6. package/express/src/framework/facade.ts +24 -5
  7. package/express/src/framework/kernel.ts +54 -52
  8. package/express/src/framework/maker-cli/index.mjs +2 -0
  9. package/express/src/framework/maker-cli/module/core.mjs +30 -0
  10. package/express/src/framework/maker-cli/module/index.mjs +10 -1
  11. package/express/src/framework/maker-cli/provider/core.mjs +49 -0
  12. package/express/src/framework/maker-cli/provider/index.mjs +12 -0
  13. package/express/src/framework/maker-cli/stubs/notification/controller.ts.stub +77 -77
  14. package/express/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  15. package/express/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  16. package/express/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  17. package/express/src/framework/maker-cli/utils/help.mjs +6 -1
  18. package/express/src/framework/maker-cli/utils/project-commands.mjs +73 -12
  19. package/express/src/framework/notification/index.ts +63 -7
  20. package/express/src/framework/providers/adapter.ts +125 -0
  21. package/express/src/framework/providers/contracts.ts +56 -0
  22. package/express/src/framework/providers/loader.ts +105 -0
  23. package/express/src/framework/providers/registry.ts +62 -0
  24. package/express/src/middlewares/auth-middleware.ts +65 -38
  25. package/express/src/providers/README.md +107 -0
  26. package/express/src/storage/database/nexwire.sqlite +0 -0
  27. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  28. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  29. package/hono/src/framework/database/app-schema.ts +37 -0
  30. package/hono/src/framework/database/connection.ts +198 -196
  31. package/hono/src/framework/database/seed.ts +35 -22
  32. package/hono/src/framework/facade.ts +48 -29
  33. package/hono/src/framework/kernel.ts +50 -48
  34. package/hono/src/framework/maker-cli/index.mjs +2 -0
  35. package/hono/src/framework/maker-cli/module/core.mjs +30 -0
  36. package/hono/src/framework/maker-cli/module/index.mjs +10 -1
  37. package/hono/src/framework/maker-cli/provider/core.mjs +49 -0
  38. package/hono/src/framework/maker-cli/provider/index.mjs +12 -0
  39. package/hono/src/framework/maker-cli/stubs/notification/controller.ts.stub +85 -85
  40. package/hono/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  41. package/hono/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  42. package/hono/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  43. package/hono/src/framework/maker-cli/utils/help.mjs +6 -1
  44. package/hono/src/framework/maker-cli/utils/project-commands.mjs +73 -12
  45. package/hono/src/framework/notification/index.ts +63 -7
  46. package/hono/src/framework/providers/adapter.ts +125 -0
  47. package/hono/src/framework/providers/contracts.ts +56 -0
  48. package/hono/src/framework/providers/loader.ts +105 -0
  49. package/hono/src/framework/providers/registry.ts +62 -0
  50. package/hono/src/middlewares/auth-middleware.ts +65 -38
  51. package/hono/src/providers/README.md +107 -0
  52. package/hono/src/storage/database/nexwire.sqlite +0 -0
  53. package/package.json +2 -2
  54. package/src/index.js +14 -6
@@ -1,29 +1,48 @@
1
- /**
2
- * Why: Central public API surface for framework consumers.
3
- * When: Modules/middlewares need framework utilities.
4
- * Where: App-layer imports should target this file.
5
- * How: Re-exports stable, reusable helpers while hiding setup internals.
6
- */
7
-
8
- export { createRoute, z } from "@hono/zod-openapi";
9
- export * as lodash from "lodash-es";
10
- export * as HttpStatusCodes from "stoker/http-status-codes";
11
- export { jsonContent } from "stoker/openapi/helpers";
12
- export { cache } from "@/framework/cache/cache.js";
13
- export { database, db } from "@/framework/database/connection.js";
14
- export { type PaginatedResult, paginate, paginateModel, paginateQuery, paginateTable } from "@/framework/database/paginate.js";
15
- export { command, dispatchCommand, dispatchEvent } from "@/framework/events/dispatcher.js";
16
- export { createRouter, group } from "@/framework/http/router.js";
17
- export { validate } from "@/framework/http/validation.js";
18
- export { notify } from "@/framework/notification/index.js";
19
- export { queue, queueJob, shouldQueue } from "@/framework/queue/queue.js";
20
- export { broadcast } from "@/framework/realtime/index.js";
21
- export { defineSchedule } from "@/framework/scheduler/scheduler.js";
22
- export { session } from "@/framework/session/session.js";
23
- export { storage } from "@/framework/storage/storage.js";
24
- export { cookie } from "@/framework/support/cookie.js";
25
- export { jwt } from "@/framework/support/jwt.js";
26
- export { logger } from "@/framework/support/logger.js";
27
- export { mail } from "@/framework/support/mail.js";
28
- export { password } from "@/framework/support/password.js";
29
- export { urls } from "@/framework/support/url.js";
1
+ /**
2
+ * Why: Central public API surface for framework consumers.
3
+ * When: Modules/middlewares need framework utilities.
4
+ * Where: App-layer imports should target this file.
5
+ * How: Re-exports stable, reusable helpers while hiding setup internals.
6
+ */
7
+
8
+ export { createRoute, z } from "@hono/zod-openapi";
9
+ export * as lodash from "lodash-es";
10
+ export * as HttpStatusCodes from "stoker/http-status-codes";
11
+ export { jsonContent } from "stoker/openapi/helpers";
12
+ export { cache } from "@/framework/cache/cache.js";
13
+ export { database, db } from "@/framework/database/connection.js";
14
+ export { type PaginatedResult, paginate, paginateModel, paginateQuery, paginateTable } from "@/framework/database/paginate.js";
15
+ export { command, dispatchCommand, dispatchEvent } from "@/framework/events/dispatcher.js";
16
+ export { createRouter, group } from "@/framework/http/router.js";
17
+ export { validate } from "@/framework/http/validation.js";
18
+ export { notify } from "@/framework/notification/index.js";
19
+ export type {
20
+ AuthProvider,
21
+ AuthProviderRefreshToken,
22
+ AuthProviderUser,
23
+ AuthUserContact,
24
+ AuthUserContext
25
+ } from "@/framework/providers/contracts.js";
26
+ export { loadProviders } from "@/framework/providers/loader.js";
27
+ export {
28
+ clearAuthProvider,
29
+ clearProvider,
30
+ getAuthProvider,
31
+ getProvider,
32
+ hasAuthProvider,
33
+ hasProvider,
34
+ listProviders,
35
+ registerAuthProvider,
36
+ registerProvider
37
+ } from "@/framework/providers/registry.js";
38
+ export { queue, queueJob, shouldQueue } from "@/framework/queue/queue.js";
39
+ export { broadcast } from "@/framework/realtime/index.js";
40
+ export { defineSchedule } from "@/framework/scheduler/scheduler.js";
41
+ export { session } from "@/framework/session/session.js";
42
+ export { storage } from "@/framework/storage/storage.js";
43
+ export { cookie } from "@/framework/support/cookie.js";
44
+ export { jwt } from "@/framework/support/jwt.js";
45
+ export { logger } from "@/framework/support/logger.js";
46
+ export { mail } from "@/framework/support/mail.js";
47
+ export { password } from "@/framework/support/password.js";
48
+ export { urls } from "@/framework/support/url.js";
@@ -1,48 +1,50 @@
1
- import { appConfig } from "@/config/index.js";
2
- import { validateConfig } from "@/config/validate.js";
3
- import { initDatabase } from "@/framework/database/connection.js";
4
- import { createHttpApp } from "@/framework/http/app.js";
5
- import { ensurePublicDir, hasUiBuild, uiIndexMiddleware, uiStaticMiddleware } from "@/framework/http/static.js";
6
- import { registerModuleRoutes } from "@/framework/modules/routes.js";
7
- import { bootQueueJobs } from "@/framework/queue/queue.js";
8
- import { setupQueueDashboard } from "@/framework/queue/ui.js";
9
- import { initRedis } from "@/framework/redis/client.js";
10
- import { storage } from "@/framework/storage/storage.js";
11
-
12
- /**
13
- * Why: Assembles app kernel and boots all framework dependencies.
14
- * When: HTTP server startup.
15
- * Where: Called by server runtime entrypoint.
16
- * How: Initializes storage/db/redis/queues/events/routes and optional UI.
17
- */
18
- export async function createKernel() {
19
- validateConfig();
20
-
21
- await storage.init();
22
-
23
- await initRedis();
24
-
25
- const app = createHttpApp();
26
-
27
- await initDatabase();
28
- await bootQueueJobs();
29
- await registerModuleRoutes(app);
30
-
31
- const queueDashboard = await setupQueueDashboard();
32
- if (typeof queueDashboard.route === "function") {
33
- queueDashboard.route(app);
34
- } else {
35
- app.route(queueDashboard.basePath, queueDashboard.route);
36
- }
37
-
38
- if (appConfig.uiEnabled) {
39
- ensurePublicDir();
40
- }
41
-
42
- if (appConfig.uiEnabled && hasUiBuild()) {
43
- app.use("/*", uiStaticMiddleware);
44
- app.get("/*", uiIndexMiddleware);
45
- }
46
-
47
- return { app, bullBoard: queueDashboard };
48
- }
1
+ import { appConfig } from "@/config/index.js";
2
+ import { validateConfig } from "@/config/validate.js";
3
+ import { initDatabase } from "@/framework/database/connection.js";
4
+ import { createHttpApp } from "@/framework/http/app.js";
5
+ import { ensurePublicDir, hasUiBuild, uiIndexMiddleware, uiStaticMiddleware } from "@/framework/http/static.js";
6
+ import { registerModuleRoutes } from "@/framework/modules/routes.js";
7
+ import { loadProviders } from "@/framework/providers/loader.js";
8
+ import { bootQueueJobs } from "@/framework/queue/queue.js";
9
+ import { setupQueueDashboard } from "@/framework/queue/ui.js";
10
+ import { initRedis } from "@/framework/redis/client.js";
11
+ import { storage } from "@/framework/storage/storage.js";
12
+
13
+ /**
14
+ * Why: Assembles app kernel and boots all framework dependencies.
15
+ * When: HTTP server startup.
16
+ * Where: Called by server runtime entrypoint.
17
+ * How: Initializes storage/db/redis/queues/events/routes and optional UI.
18
+ */
19
+ export async function createKernel() {
20
+ validateConfig();
21
+
22
+ await storage.init();
23
+
24
+ await initRedis();
25
+
26
+ const app = createHttpApp();
27
+
28
+ await initDatabase();
29
+ await loadProviders();
30
+ await bootQueueJobs();
31
+ await registerModuleRoutes(app);
32
+
33
+ const queueDashboard = await setupQueueDashboard();
34
+ if (typeof queueDashboard.route === "function") {
35
+ queueDashboard.route(app);
36
+ } else {
37
+ app.route(queueDashboard.basePath, queueDashboard.route);
38
+ }
39
+
40
+ if (appConfig.uiEnabled) {
41
+ ensurePublicDir();
42
+ }
43
+
44
+ if (appConfig.uiEnabled && hasUiBuild()) {
45
+ app.use("/*", uiStaticMiddleware);
46
+ app.get("/*", uiIndexMiddleware);
47
+ }
48
+
49
+ return { app, bullBoard: queueDashboard };
50
+ }
@@ -5,6 +5,7 @@ import { config } from "dotenv";
5
5
  import { registerDbCommands } from "./db/index.mjs";
6
6
  import { registerMiddlewareCommands } from "./middleware/index.mjs";
7
7
  import { registerModuleCommands } from "./module/index.mjs";
8
+ import { registerProviderCommands } from "./provider/index.mjs";
8
9
  import { registerRuntimeCommands } from "./runtime/index.mjs";
9
10
  import { configureGroupedHelp, makerCommandPrefix, showHelp } from "./utils/help.mjs";
10
11
  import { registerProjectCommands } from "./utils/project-commands.mjs";
@@ -42,6 +43,7 @@ try {
42
43
 
43
44
  registerMiddlewareCommands(program, args);
44
45
  registerModuleCommands(program, args);
46
+ registerProviderCommands(program, args);
45
47
  registerDeployCommands(program, args);
46
48
  registerDbCommands(program, args);
47
49
  registerRuntimeCommands(program, args);
@@ -45,6 +45,11 @@ const STUBS = {
45
45
  facade: {
46
46
  named: "facade/name.ts.stub"
47
47
  },
48
+ provider: {
49
+ named: "provider/name.ts.stub",
50
+ module: "provider/module.ts.stub",
51
+ moduleBarrel: "provider/module-barrel.ts.stub"
52
+ },
48
53
  helper: {
49
54
  named: "helper/name.ts.stub"
50
55
  },
@@ -736,6 +741,31 @@ export async function makeLocalMiddleware(rawModule, rawNameOrFlag, extraFlags =
736
741
  await writeFileSafe(path.join(root, `middlewares/${name}.ts`), content, flags, "Middleware file");
737
742
  }
738
743
 
744
+ /**
745
+ * Generate a provider file for an existing module.
746
+ *
747
+ * The file lands in the module's providers/ folder, which the framework
748
+ * imports at boot just like src/providers/. That is what lets a module offer
749
+ * an implementation to the whole application without the application knowing
750
+ * anything about the module's internals.
751
+ */
752
+ export async function makeModuleProvider(rawModule, rawNameOrFlag, extraFlags = []) {
753
+ const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
754
+ const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Provider name", leaf);
755
+ const root = await assertModuleExists(moduleName);
756
+ const filePath = path.join(root, `providers/${name}.ts`);
757
+ if (hasFlag(flags, "--dry-run")) {
758
+ console.log(`Would create provider file: ${path.relative(process.cwd(), filePath)}`);
759
+ return;
760
+ }
761
+ const content = await stub(STUBS.provider.module, {
762
+ module: moduleName,
763
+ name,
764
+ camel: camelCase(name)
765
+ });
766
+ await writeFileSafe(filePath, content, flags, "Provider file");
767
+ }
768
+
739
769
  /** Generate a types file for an existing module. */
740
770
  export async function makeType(rawModule, rawNameOrFlag, extraFlags = []) {
741
771
  const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
@@ -12,6 +12,7 @@ import {
12
12
  makeLocalMiddleware,
13
13
  makeModel,
14
14
  makeModule,
15
+ makeModuleProvider,
15
16
  makeModules,
16
17
  makeNotificationModule,
17
18
  makeRoute,
@@ -28,7 +29,7 @@ import {
28
29
  export function registerModuleCommands(program, rawArgs) {
29
30
  program
30
31
  .command("module:make <names...>")
31
- .description("Create one or more modules with facade, schema, service, controller, route, model, and seeder folders")
32
+ .description("Create one or more modules with facade, providers, schema, service, controller, route, model, and seeder folders")
32
33
  .option("--path <panel>", "Nest the module(s) under a panel folder (e.g. --path=admin)")
33
34
  .option("--force", "Overwrite the files of modules that already exist")
34
35
  .allowUnknownOption(true)
@@ -128,6 +129,14 @@ export function registerModuleCommands(program, rawArgs) {
128
129
  .option("--path <panel>", "Address a module inside a panel folder (e.g. --path=admin)")
129
130
  .allowUnknownOption(true)
130
131
  .action(async (moduleName, name) => makeLocalMiddleware(moduleName, name, rawArgs.slice(name ? 3 : 2)));
132
+ program
133
+ .command("module:make-provider <module> [name]")
134
+ .description("Generate a provider file in the module's providers folder")
135
+ .option("--force", "Overwrite existing provider file")
136
+ .option("--dry-run", "Print what would be created without executing")
137
+ .option("--path <panel>", "Address a module inside a panel folder (e.g. --path=admin)")
138
+ .allowUnknownOption(true)
139
+ .action(async (moduleName, name) => makeModuleProvider(moduleName, name, rawArgs.slice(name ? 3 : 2)));
131
140
  program
132
141
  .command("module:make-type <module> [name]")
133
142
  .description("Generate a types file for an existing module")
@@ -0,0 +1,49 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { hasFlag } from "../utils/flags.mjs";
4
+ import { assertName, camelCase } from "../utils/naming.mjs";
5
+
6
+ const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
7
+
8
+ /** Read a stub file and replace {{key}} placeholders, removing unfilled ones. */
9
+ async function stub(name, values = {}) {
10
+ let content = await fs.readFile(path.join(stubsRoot, name), "utf8");
11
+ for (const [key, value] of Object.entries(values)) {
12
+ content = content.replaceAll(`{{${key}}}`, String(value));
13
+ }
14
+ return content.replace(/\{\{[A-Z0-9_]+\}\}/g, "");
15
+ }
16
+
17
+ /**
18
+ * Why: Generates the app-wide provider file.
19
+ * When: A developer wants to plug an implementation into the whole app.
20
+ * Where: src/providers/<name>.ts - imported automatically at boot.
21
+ * How: Writes the stub and prints how to reach the provider from anywhere.
22
+ */
23
+ export async function makeProvider(rawName, flags = []) {
24
+ const name = assertName(rawName, "Provider name");
25
+ const camel = camelCase(name);
26
+ const dryRun = hasFlag(flags, "--dry-run");
27
+ const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
28
+
29
+ const filePath = path.resolve(process.cwd(), "src/providers", `${name}.ts`);
30
+ if (dryRun) {
31
+ console.log(`Would create provider file: ${path.relative(process.cwd(), filePath)}`);
32
+ return;
33
+ }
34
+
35
+ let exists = false;
36
+ try {
37
+ await fs.access(filePath);
38
+ exists = true;
39
+ } catch {}
40
+ if (exists && !force) {
41
+ throw new Error(`Provider already exists: ${path.relative(process.cwd(), filePath)}. Re-run with --force to overwrite.`);
42
+ }
43
+
44
+ const content = await stub("provider/name.ts.stub", { name, camel });
45
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
46
+ await fs.writeFile(filePath, content);
47
+ console.log(`Provider ready: ${path.relative(process.cwd(), filePath)}`);
48
+ console.log(`It is imported at boot. Reach it with: getProvider("${name}")`);
49
+ }
@@ -0,0 +1,12 @@
1
+ import { makeProvider } from "./core.mjs";
2
+
3
+ /** Register provider:* subcommands on the CLI program. */
4
+ export function registerProviderCommands(program, rawArgs) {
5
+ program
6
+ .command("provider:make <name>")
7
+ .description("Generate a provider file in src/providers")
8
+ .option("--force", "Overwrite existing provider file")
9
+ .option("--dry-run", "Print what would be created without executing")
10
+ .allowUnknownOption(true)
11
+ .action(async (name) => makeProvider(name, rawArgs.filter((arg) => arg.startsWith("--"))));
12
+ }
@@ -1,86 +1,86 @@
1
- import type { Handler } from "hono";
2
- import { and, count, desc, eq, sql } from "drizzle-orm";
3
- import { db } from "@/framework/facade.js";
4
- import { notifications } from "@/modules/auth/database/models/notifications.js";
5
-
6
- export const list: Handler = async (c: any) => {
7
- const userId = c.get("auth").id;
8
- const page = Math.max(1, Number(c.req.query("page")) || 1);
9
- const perPage = Math.min(50, Math.max(1, Number(c.req.query("perPage")) || 20));
10
-
11
- const unread = await db.select({ count: count() }).from(notifications)
12
- .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
13
-
14
- const total = await db.select({ count: count() }).from(notifications)
15
- .where(eq(notifications.userId, userId));
16
-
17
- const rows = await db.select().from(notifications)
18
- .where(eq(notifications.userId, userId))
19
- .orderBy(desc(notifications.createdAt))
20
- .limit(perPage).offset((page - 1) * perPage);
21
-
22
- return c.json({
23
- data: rows.map(normalize),
24
- total: total[0].count,
25
- unread: unread[0].count,
26
- page,
27
- perPage,
28
- });
29
- };
30
-
31
- export const unreadCount: Handler = async (c: any) => {
32
- const userId = c.get("auth").id;
33
- const rows = await db.select({ count: count() }).from(notifications)
34
- .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
35
- return c.json({ count: rows[0].count });
36
- };
37
-
38
- export const markRead: Handler = async (c: any) => {
39
- const userId = c.get("auth").id;
40
- const id = Number(c.req.param("id"));
41
-
42
- await db.update(notifications)
43
- .set({ readAt: sql`now()` })
44
- .where(and(eq(notifications.id, id), eq(notifications.userId, userId)));
45
-
46
- return c.json({ ok: true });
47
- };
48
-
49
- export const markAllRead: Handler = async (c: any) => {
50
- const userId = c.get("auth").id;
51
-
52
- await db.update(notifications)
53
- .set({ readAt: sql`now()` })
54
- .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
55
-
56
- return c.json({ ok: true });
57
- };
58
-
59
- export const remove: Handler = async (c: any) => {
60
- const userId = c.get("auth").id;
61
- const id = Number(c.req.param("id"));
62
-
63
- await db.delete(notifications)
64
- .where(and(eq(notifications.id, id), eq(notifications.userId, userId)));
65
-
66
- return c.json({ ok: true });
67
- };
68
-
69
- function normalize(row: typeof notifications.$inferSelect) {
70
- let parsed: unknown = null;
71
- if (row.data) {
72
- try { parsed = JSON.parse(row.data); } catch {}
73
- }
74
- return {
75
- id: row.id,
76
- userId: row.userId,
77
- type: row.type,
78
- title: row.title,
79
- body: row.body,
80
- data: parsed,
81
- link: row.link,
82
- readAt: row.readAt?.toISOString() || null,
83
- createdAt: row.createdAt.toISOString(),
84
- };
85
- }
1
+ import type { Handler } from "hono";
2
+ import { and, count, desc, eq, sql } from "drizzle-orm";
3
+ import { db } from "@/framework/facade.js";
4
+ import { notifications } from "@/modules/auth/database/models/notification.js";
5
+
6
+ export const list: Handler = async (c: any) => {
7
+ const userId = c.get("auth").id;
8
+ const page = Math.max(1, Number(c.req.query("page")) || 1);
9
+ const perPage = Math.min(50, Math.max(1, Number(c.req.query("perPage")) || 20));
10
+
11
+ const unread = await db.select({ count: count() }).from(notifications)
12
+ .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
13
+
14
+ const total = await db.select({ count: count() }).from(notifications)
15
+ .where(eq(notifications.userId, userId));
16
+
17
+ const rows = await db.select().from(notifications)
18
+ .where(eq(notifications.userId, userId))
19
+ .orderBy(desc(notifications.createdAt))
20
+ .limit(perPage).offset((page - 1) * perPage);
21
+
22
+ return c.json({
23
+ data: rows.map(normalize),
24
+ total: total[0].count,
25
+ unread: unread[0].count,
26
+ page,
27
+ perPage,
28
+ });
29
+ };
30
+
31
+ export const unreadCount: Handler = async (c: any) => {
32
+ const userId = c.get("auth").id;
33
+ const rows = await db.select({ count: count() }).from(notifications)
34
+ .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
35
+ return c.json({ count: rows[0].count });
36
+ };
37
+
38
+ export const markRead: Handler = async (c: any) => {
39
+ const userId = c.get("auth").id;
40
+ const id = Number(c.req.param("id"));
41
+
42
+ await db.update(notifications)
43
+ .set({ readAt: sql`now()` })
44
+ .where(and(eq(notifications.id, id), eq(notifications.userId, userId)));
45
+
46
+ return c.json({ ok: true });
47
+ };
48
+
49
+ export const markAllRead: Handler = async (c: any) => {
50
+ const userId = c.get("auth").id;
51
+
52
+ await db.update(notifications)
53
+ .set({ readAt: sql`now()` })
54
+ .where(and(eq(notifications.userId, userId), sql`${notifications.readAt} IS NULL`));
55
+
56
+ return c.json({ ok: true });
57
+ };
58
+
59
+ export const remove: Handler = async (c: any) => {
60
+ const userId = c.get("auth").id;
61
+ const id = Number(c.req.param("id"));
62
+
63
+ await db.delete(notifications)
64
+ .where(and(eq(notifications.id, id), eq(notifications.userId, userId)));
65
+
66
+ return c.json({ ok: true });
67
+ };
68
+
69
+ function normalize(row: typeof notifications.$inferSelect) {
70
+ let parsed: unknown = null;
71
+ if (row.data) {
72
+ try { parsed = JSON.parse(row.data); } catch {}
73
+ }
74
+ return {
75
+ id: row.id,
76
+ userId: row.userId,
77
+ type: row.type,
78
+ title: row.title,
79
+ body: row.body,
80
+ data: parsed,
81
+ link: row.link,
82
+ readAt: row.readAt?.toISOString() || null,
83
+ createdAt: row.createdAt.toISOString(),
84
+ };
85
+ }
86
86
 
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Providers for the {{module}} module.
3
+ *
4
+ * This file is imported automatically at boot, so whatever it registers is
5
+ * available to the whole application. Use this to plug implementations (e.g.
6
+ * auth, payments) into the app - not to export the module's API to other
7
+ * modules (that is what facade.ts is for):
8
+ *
9
+ * import { registerProvider } from "@/framework/facade.js";
10
+ *
11
+ * registerProvider("{{module}}", { ... });
12
+ *
13
+ * A module with more than one provider keeps them in providers/*.ts instead -
14
+ * those files are imported at boot too.
15
+ *
16
+ * Rule: a module never imports another module's provider implementation.
17
+ */
18
+
19
+ export {};
@@ -0,0 +1,21 @@
1
+ import { registerProvider } from "@/framework/facade.js";
2
+
3
+ /**
4
+ * Why: Plugs the {{name}} implementation into the application.
5
+ * When: Imported automatically at boot from each module's providers folder.
6
+ * Where: src/modules/{{module}}/providers/{{name}}.ts
7
+ *
8
+ * Reach it from anywhere in the app:
9
+ *
10
+ * import { getProvider } from "@/framework/facade.js";
11
+ * const {{camel}} = getProvider<typeof {{camel}}Provider>("{{name}}");
12
+ *
13
+ * Rule: a provider never imports another module's provider implementation.
14
+ */
15
+ export const {{camel}}Provider = {
16
+ async handle(input: unknown) {
17
+ return input;
18
+ }
19
+ };
20
+
21
+ registerProvider("{{name}}", {{camel}}Provider);
@@ -0,0 +1,21 @@
1
+ import { registerProvider } from "@/framework/facade.js";
2
+
3
+ /**
4
+ * Why: Plugs the {{name}} implementation into the application.
5
+ * When: Imported automatically at boot from src/providers/.
6
+ * Where: src/providers/{{name}}.ts
7
+ *
8
+ * Reach it from anywhere in the app:
9
+ *
10
+ * import { getProvider } from "@/framework/facade.js";
11
+ * const {{camel}} = getProvider<typeof {{camel}}Provider>("{{name}}");
12
+ *
13
+ * Rule: a provider never imports another provider's implementation.
14
+ */
15
+ export const {{camel}}Provider = {
16
+ async handle(input: unknown) {
17
+ return input;
18
+ }
19
+ };
20
+
21
+ registerProvider("{{name}}", {{camel}}Provider);
@@ -89,7 +89,11 @@ const COMMAND_GROUPS = [
89
89
  // `module:make-middleware` writes inside one module and applies only to it.
90
90
  // In the same section the two look interchangeable, which is exactly the
91
91
  // mistake a developer is about to make.
92
- commands: ["middleware:make"]
92
+ //
93
+ // `provider:make` follows the same split: it writes to src/providers and
94
+ // applies to the whole app, `module:make-provider` writes inside one
95
+ // module and applies only to it.
96
+ commands: ["middleware:make", "provider:make"]
93
97
  },
94
98
  {
95
99
  title: "Generators - modules",
@@ -107,6 +111,7 @@ const COMMAND_GROUPS = [
107
111
  "module:make-test",
108
112
  "module:make-helper",
109
113
  "module:make-middleware",
114
+ "module:make-provider",
110
115
  "module:make-type"
111
116
  ]
112
117
  },