@simplysm/service-server 13.0.100 → 14.0.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 (83) hide show
  1. package/dist/auth/auth-token-payload.js +2 -1
  2. package/dist/auth/auth-token-payload.js.map +1 -6
  3. package/dist/auth/jwt-manager.js +21 -21
  4. package/dist/auth/jwt-manager.js.map +1 -6
  5. package/dist/core/define-service.d.ts +12 -12
  6. package/dist/core/define-service.d.ts.map +1 -1
  7. package/dist/core/define-service.js +77 -63
  8. package/dist/core/define-service.js.map +1 -6
  9. package/dist/core/service-executor.d.ts.map +1 -1
  10. package/dist/core/service-executor.js +42 -32
  11. package/dist/core/service-executor.js.map +1 -6
  12. package/dist/index.d.ts +0 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +11 -2
  15. package/dist/index.js.map +1 -6
  16. package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
  17. package/dist/legacy/v1-auto-update-handler.js +42 -35
  18. package/dist/legacy/v1-auto-update-handler.js.map +1 -6
  19. package/dist/protocol/protocol-wrapper.d.ts +9 -9
  20. package/dist/protocol/protocol-wrapper.js +64 -46
  21. package/dist/protocol/protocol-wrapper.js.map +1 -6
  22. package/dist/service-server.d.ts +2 -0
  23. package/dist/service-server.d.ts.map +1 -1
  24. package/dist/service-server.js +187 -165
  25. package/dist/service-server.js.map +1 -6
  26. package/dist/services/auto-update-service.js +35 -34
  27. package/dist/services/auto-update-service.js.map +1 -6
  28. package/dist/services/orm-service.js +114 -120
  29. package/dist/services/orm-service.js.map +1 -6
  30. package/dist/transport/http/http-request-handler.d.ts.map +1 -1
  31. package/dist/transport/http/http-request-handler.js +58 -46
  32. package/dist/transport/http/http-request-handler.js.map +1 -6
  33. package/dist/transport/http/static-file-handler.js +42 -39
  34. package/dist/transport/http/static-file-handler.js.map +1 -6
  35. package/dist/transport/http/upload-handler.d.ts.map +1 -1
  36. package/dist/transport/http/upload-handler.js +60 -55
  37. package/dist/transport/http/upload-handler.js.map +1 -6
  38. package/dist/transport/socket/service-socket.d.ts +13 -13
  39. package/dist/transport/socket/service-socket.js +132 -108
  40. package/dist/transport/socket/service-socket.js.map +1 -6
  41. package/dist/transport/socket/websocket-handler.d.ts +10 -10
  42. package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
  43. package/dist/transport/socket/websocket-handler.js +154 -139
  44. package/dist/transport/socket/websocket-handler.js.map +1 -6
  45. package/dist/types/server-options.d.ts +1 -1
  46. package/dist/types/server-options.d.ts.map +1 -1
  47. package/dist/types/server-options.js +2 -1
  48. package/dist/types/server-options.js.map +1 -6
  49. package/dist/utils/config-manager.js +48 -46
  50. package/dist/utils/config-manager.js.map +1 -6
  51. package/dist/workers/service-protocol.worker.js +8 -11
  52. package/dist/workers/service-protocol.worker.js.map +1 -6
  53. package/package.json +12 -14
  54. package/src/auth/jwt-manager.ts +2 -2
  55. package/src/core/define-service.ts +19 -19
  56. package/src/core/service-executor.ts +23 -17
  57. package/src/index.ts +10 -12
  58. package/src/legacy/v1-auto-update-handler.ts +10 -10
  59. package/src/protocol/protocol-wrapper.ts +16 -16
  60. package/src/service-server.ts +52 -39
  61. package/src/services/auto-update-service.ts +1 -1
  62. package/src/services/orm-service.ts +7 -7
  63. package/src/transport/http/http-request-handler.ts +16 -10
  64. package/src/transport/http/static-file-handler.ts +8 -8
  65. package/src/transport/http/upload-handler.ts +16 -9
  66. package/src/transport/socket/service-socket.ts +22 -22
  67. package/src/transport/socket/websocket-handler.ts +59 -60
  68. package/src/types/server-options.ts +1 -1
  69. package/src/utils/config-manager.ts +11 -11
  70. package/README.md +0 -163
  71. package/dist/services/smtp-client-service.d.ts +0 -8
  72. package/dist/services/smtp-client-service.d.ts.map +0 -1
  73. package/dist/services/smtp-client-service.js +0 -46
  74. package/dist/services/smtp-client-service.js.map +0 -6
  75. package/docs/auth.md +0 -59
  76. package/docs/core.md +0 -133
  77. package/docs/server.md +0 -126
  78. package/docs/services.md +0 -58
  79. package/docs/transport.md +0 -164
  80. package/src/services/smtp-client-service.ts +0 -59
  81. package/tests/define-service.spec.ts +0 -66
  82. package/tests/orm-service.spec.ts +0 -83
  83. package/tests/service-executor.spec.ts +0 -114
package/docs/transport.md DELETED
@@ -1,164 +0,0 @@
1
- # Transport
2
-
3
- ## WebSocket Transport
4
-
5
- ### `WebSocketHandler`
6
-
7
- WebSocket handler interface. Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
8
-
9
- ```typescript
10
- interface WebSocketHandler {
11
- addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
12
- closeAll(): void;
13
- broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
14
- emit<TInfo, TData>(
15
- eventDef: ServiceEventDef<TInfo, TData>,
16
- infoSelector: (item: TInfo) => boolean,
17
- data: TData,
18
- ): Promise<void>;
19
- }
20
- ```
21
-
22
- | Method | Description |
23
- |--------|-------------|
24
- | `addSocket()` | Add a new WebSocket connection |
25
- | `closeAll()` | Close all active connections |
26
- | `broadcastReload()` | Broadcast reload message to all clients |
27
- | `emit()` | Emit event to matching clients |
28
-
29
- ### `createWebSocketHandler`
30
-
31
- Create a WebSocket handler instance.
32
-
33
- ```typescript
34
- function createWebSocketHandler(
35
- runMethod: (def: {
36
- serviceName: string;
37
- methodName: string;
38
- params: unknown[];
39
- socket: ServiceSocket;
40
- }) => Promise<unknown>,
41
- jwtSecret?: string,
42
- ): WebSocketHandler;
43
- ```
44
-
45
- ### `ServiceSocket`
46
-
47
- Service socket interface. Manages a single WebSocket connection with protocol encoding/decoding, ping/pong keep-alive, and event listener tracking.
48
-
49
- ```typescript
50
- interface ServiceSocket {
51
- readonly connectedAtDateTime: DateTime;
52
- readonly clientName: string;
53
- readonly connReq: FastifyRequest;
54
- authTokenPayload?: AuthTokenPayload;
55
-
56
- close(): void;
57
- send(uuid: string, msg: ServiceServerMessage): Promise<number>;
58
- addListener(key: string, eventName: string, info: unknown): void;
59
- removeListener(key: string): void;
60
- getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
61
- filterEventTargetKeys(targetKeys: string[]): string[];
62
- on(event: "error", handler: (err: Error) => void): void;
63
- on(event: "close", handler: (code: number) => void): void;
64
- on(event: "message", handler: (data: { uuid: string; msg: ServiceClientMessage }) => void): void;
65
- }
66
- ```
67
-
68
- | Property | Type | Description |
69
- |----------|------|-------------|
70
- | `connectedAtDateTime` | `DateTime` | Connection time |
71
- | `clientName` | `string` | Client name |
72
- | `connReq` | `FastifyRequest` | Original Fastify request |
73
- | `authTokenPayload` | `AuthTokenPayload` | Authenticated token payload |
74
-
75
- | Method | Description |
76
- |--------|-------------|
77
- | `close()` | Close the WebSocket connection |
78
- | `send()` | Send a message to the client |
79
- | `addListener()` | Register an event listener |
80
- | `removeListener()` | Remove an event listener |
81
- | `getEventListeners()` | Get all listeners for an event name |
82
- | `filterEventTargetKeys()` | Filter target keys that exist in this socket |
83
- | `on()` | Register event handlers (error, close, message) |
84
-
85
- ### `createServiceSocket`
86
-
87
- Create a service socket instance.
88
-
89
- ```typescript
90
- function createServiceSocket(
91
- socket: WebSocket,
92
- clientId: string,
93
- clientName: string,
94
- connReq: FastifyRequest,
95
- ): ServiceSocket;
96
- ```
97
-
98
- ## HTTP Transport
99
-
100
- ### `handleHttpRequest`
101
-
102
- Handle HTTP API requests. Routes `POST/GET /api/:service/:method` to service methods.
103
-
104
- ```typescript
105
- async function handleHttpRequest<TAuthInfo = unknown>(
106
- req: FastifyRequest,
107
- reply: FastifyReply,
108
- jwtSecret: string | undefined,
109
- runMethod: (def: {
110
- serviceName: string;
111
- methodName: string;
112
- params: unknown[];
113
- http: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> };
114
- }) => Promise<unknown>,
115
- ): Promise<void>;
116
- ```
117
-
118
- ### `handleUpload`
119
-
120
- Handle file upload requests. Accepts multipart form data with auth token.
121
-
122
- ```typescript
123
- async function handleUpload(
124
- req: FastifyRequest,
125
- reply: FastifyReply,
126
- rootPath: string,
127
- jwtSecret: string | undefined,
128
- ): Promise<void>;
129
- ```
130
-
131
- ### `handleStaticFile`
132
-
133
- Handle static file serving. Serves files from `www/` directory with security checks (path traversal protection, hidden file blocking).
134
-
135
- ```typescript
136
- async function handleStaticFile(
137
- req: FastifyRequest,
138
- reply: FastifyReply,
139
- rootPath: string,
140
- urlPath: string,
141
- ): Promise<void>;
142
- ```
143
-
144
- ## Protocol
145
-
146
- ### `ServerProtocolWrapper`
147
-
148
- Server-side protocol wrapper interface. Automatically offloads heavy encoding/decoding to a worker thread (>30KB threshold).
149
-
150
- ```typescript
151
- interface ServerProtocolWrapper {
152
- encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
153
- decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
154
- dispose(): void;
155
- }
156
- ```
157
-
158
- ### `createServerProtocolWrapper`
159
-
160
- Create a server protocol wrapper instance.
161
-
162
- ```typescript
163
- function createServerProtocolWrapper(): ServerProtocolWrapper;
164
- ```
@@ -1,59 +0,0 @@
1
- import nodemailer from "nodemailer";
2
- import { defineService, type ServiceMethods } from "../core/define-service";
3
- import type {
4
- SmtpClientDefaultOptions,
5
- SmtpClientSendByDefaultOption,
6
- SmtpClientSendOption,
7
- } from "@simplysm/service-common";
8
-
9
- export const SmtpClientService = defineService("SmtpClient", (ctx) => ({
10
- async send(options: SmtpClientSendOption): Promise<string> {
11
- return new Promise<string>((resolve, reject) => {
12
- const transport = nodemailer.createTransport({
13
- host: options.host,
14
- port: options.port,
15
- secure: options.secure,
16
- auth:
17
- options.user != null
18
- ? {
19
- user: options.user,
20
- pass: options.pass,
21
- }
22
- : undefined,
23
- tls: {
24
- rejectUnauthorized: false,
25
- },
26
- });
27
-
28
- transport.sendMail(options as nodemailer.SendMailOptions, (err, info) => {
29
- if (err) {
30
- reject(err);
31
- return;
32
- }
33
-
34
- resolve(info.messageId);
35
- });
36
- });
37
- },
38
-
39
- async sendByConfig(configName: string, options: SmtpClientSendByDefaultOption): Promise<string> {
40
- const config = (
41
- await ctx.getConfig<Record<string, SmtpClientDefaultOptions | undefined>>("smtp")
42
- )[configName];
43
- if (config == null) {
44
- throw new Error(`SMTP config not found: ${configName}`);
45
- }
46
-
47
- return this.send({
48
- user: config.user,
49
- pass: config.pass,
50
- host: config.host,
51
- port: config.port,
52
- secure: config.secure,
53
- from: `"${config.senderName}" <${config.senderEmail ?? config.user}>`,
54
- ...options,
55
- });
56
- },
57
- }));
58
-
59
- export type SmtpClientServiceType = ServiceMethods<typeof SmtpClientService>;
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { defineService, auth, getServiceAuthPermissions } from "@simplysm/service-server";
3
-
4
- describe("defineService", () => {
5
- it("creates service definition with name and factory", () => {
6
- const svc = defineService("Health", (_ctx) => ({
7
- check: () => "ok",
8
- }));
9
-
10
- expect(svc.name).toBe("Health");
11
- expect(typeof svc.factory).toBe("function");
12
- });
13
-
14
- it("factory generates methods when called with context", () => {
15
- const svc = defineService("Echo", (_ctx) => ({
16
- echo: (msg: string) => `Echo: ${msg}`,
17
- }));
18
-
19
- const methods = svc.factory({} as any);
20
- expect(methods.echo("hello")).toBe("Echo: hello");
21
- });
22
- });
23
-
24
- describe("Authentication", () => {
25
- it("marks function with empty permissions (login required only)", () => {
26
- const fn = auth(() => "result");
27
- expect(getServiceAuthPermissions(fn)).toEqual([]);
28
- expect(fn()).toBe("result");
29
- });
30
-
31
- it("marks function with specific permissions", () => {
32
- const fn = auth(["admin"], (id: number) => id * 2);
33
- expect(getServiceAuthPermissions(fn)).toEqual(["admin"]);
34
- expect(fn(5)).toBe(10);
35
- });
36
-
37
- it("returns undefined for unmarked function", () => {
38
- const fn = () => "plain";
39
- expect(getServiceAuthPermissions(fn)).toBeUndefined();
40
- });
41
-
42
- it("works at service level (factory wrapping)", () => {
43
- const svc = defineService(
44
- "User",
45
- auth((_ctx) => ({
46
- getProfile: () => "profile",
47
- })),
48
- );
49
-
50
- expect(svc.authPermissions).toEqual([]);
51
- });
52
-
53
- it("method-level auth is readable from returned methods", () => {
54
- const svc = defineService(
55
- "Mixed",
56
- auth((_ctx) => ({
57
- normal: () => "normal",
58
- adminOnly: auth(["admin"], () => "admin"),
59
- })),
60
- );
61
-
62
- const methods = svc.factory({} as any);
63
- expect(getServiceAuthPermissions(methods.normal)).toBeUndefined();
64
- expect(getServiceAuthPermissions(methods.adminOnly)).toEqual(["admin"]);
65
- });
66
- });
@@ -1,83 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import type { QueryDef } from "@simplysm/orm-common";
3
-
4
- vi.mock("@simplysm/orm-node", () => ({
5
- createDbConn: vi.fn(),
6
- }));
7
-
8
- import { createDbConn } from "@simplysm/orm-node";
9
- import { OrmService } from "../src/services/orm-service";
10
-
11
- describe("OrmService.executeDefs", () => {
12
- let mockExecute: ReturnType<typeof vi.fn>;
13
- let methods: any;
14
- let connId: number;
15
-
16
- const twoDefs: QueryDef[] = [
17
- {
18
- type: "createTable",
19
- table: { database: "db", name: "A" },
20
- columns: [{ name: "id", dataType: { type: "bigint" } }],
21
- primaryKey: ["id"],
22
- },
23
- {
24
- type: "createTable",
25
- table: { database: "db", name: "B" },
26
- columns: [{ name: "id", dataType: { type: "bigint" } }],
27
- primaryKey: ["id"],
28
- },
29
- ];
30
-
31
- beforeEach(async () => {
32
- mockExecute = vi.fn((queries: string[]) => Promise.resolve(queries.map(() => [])));
33
-
34
- const mockConn = {
35
- config: { dialect: "postgresql" as const },
36
- isConnected: true,
37
- connect: vi.fn(),
38
- close: vi.fn(),
39
- execute: mockExecute,
40
- executeParametrized: vi.fn(),
41
- bulkInsert: vi.fn(),
42
- beginTransaction: vi.fn(),
43
- commitTransaction: vi.fn(),
44
- rollbackTransaction: vi.fn(),
45
- on: vi.fn(),
46
- };
47
-
48
- vi.mocked(createDbConn).mockResolvedValue(mockConn as any);
49
-
50
- const ctx = {
51
- socket: { on: vi.fn() },
52
- getConfig: vi.fn(() =>
53
- Promise.resolve({
54
- test: { dialect: "postgresql", host: "localhost", database: "db" },
55
- }),
56
- ),
57
- clientName: "test",
58
- };
59
-
60
- methods = OrmService.factory(ctx as any);
61
- connId = await methods.connect({ configName: "test" });
62
- mockExecute.mockClear();
63
- });
64
-
65
- it("executes queries individually when options is undefined", async () => {
66
- const result = await methods.executeDefs(connId, twoDefs);
67
-
68
- // Should pass 2 separate queries, not 1 combined string
69
- expect(mockExecute).toHaveBeenCalledTimes(1);
70
- expect(mockExecute.mock.calls[0][0]).toHaveLength(2);
71
-
72
- // Should return one result per def
73
- expect(result).toHaveLength(2);
74
- });
75
-
76
- it("combines queries when options is explicitly all-null", async () => {
77
- await methods.executeDefs(connId, twoDefs, [undefined, undefined]);
78
-
79
- // Should pass 1 combined query string
80
- expect(mockExecute).toHaveBeenCalledTimes(1);
81
- expect(mockExecute.mock.calls[0][0]).toHaveLength(1);
82
- });
83
- });
@@ -1,114 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { executeServiceMethod } from "../src/core/service-executor";
3
- import { defineService, auth } from "../src/core/define-service";
4
-
5
- // Minimal mock server
6
- function createMockServer(services: any[]) {
7
- return { options: { services, auth: { jwtSecret: "test" } } } as any;
8
- }
9
-
10
- describe("executeServiceMethod with ServiceDefinition", () => {
11
- it("executes a basic service method", async () => {
12
- const EchoService = defineService("Echo", (_ctx) => ({
13
- echo: (msg: string) => `Echo: ${msg}`,
14
- }));
15
-
16
- const server = createMockServer([EchoService]);
17
- const result = await executeServiceMethod(server, {
18
- serviceName: "Echo",
19
- methodName: "echo",
20
- params: ["hello"],
21
- });
22
-
23
- expect(result).toBe("Echo: hello");
24
- });
25
-
26
- it("throws error when service not found", async () => {
27
- const server = createMockServer([]);
28
-
29
- await expect(
30
- executeServiceMethod(server, { serviceName: "Unknown", methodName: "test", params: [] }),
31
- ).rejects.toThrow("Service [Unknown] not found.");
32
- });
33
-
34
- it("throws error when method not found", async () => {
35
- const svc = defineService("Test", (_ctx) => ({
36
- existing: () => "ok",
37
- }));
38
- const server = createMockServer([svc]);
39
-
40
- await expect(
41
- executeServiceMethod(server, { serviceName: "Test", methodName: "nonexistent", params: [] }),
42
- ).rejects.toThrow("Method [Test.nonexistent] not found.");
43
- });
44
-
45
- it("blocks unauthenticated access to auth-required service", async () => {
46
- const svc = defineService(
47
- "Protected",
48
- auth((_ctx) => ({
49
- secret: () => "secret",
50
- })),
51
- );
52
- const server = createMockServer([svc]);
53
-
54
- await expect(
55
- executeServiceMethod(server, { serviceName: "Protected", methodName: "secret", params: [] }),
56
- ).rejects.toThrow("Login is required.");
57
- });
58
-
59
- it("blocks unauthorized role access", async () => {
60
- const svc = defineService(
61
- "Admin",
62
- auth((_ctx) => ({
63
- manage: auth(["admin"], () => "managed"),
64
- view: () => "viewed",
65
- })),
66
- );
67
- const server = createMockServer([svc]);
68
-
69
- // Has auth but wrong role
70
- await expect(
71
- executeServiceMethod(server, {
72
- serviceName: "Admin",
73
- methodName: "manage",
74
- params: [],
75
- http: { clientName: "test", authTokenPayload: { roles: ["user"], data: {} } as any },
76
- }),
77
- ).rejects.toThrow("Insufficient permissions.");
78
- });
79
-
80
- it("allows access with correct role", async () => {
81
- const svc = defineService(
82
- "Admin",
83
- auth((_ctx) => ({
84
- manage: auth(["admin"], () => "managed"),
85
- })),
86
- );
87
- const server = createMockServer([svc]);
88
-
89
- const result = await executeServiceMethod(server, {
90
- serviceName: "Admin",
91
- methodName: "manage",
92
- params: [],
93
- http: { clientName: "test", authTokenPayload: { roles: ["admin"], data: {} } as any },
94
- });
95
-
96
- expect(result).toBe("managed");
97
- });
98
-
99
- it("provides context to factory", async () => {
100
- const svc = defineService("Ctx", (ctx) => ({
101
- getClientName: () => ctx.clientName,
102
- }));
103
- const server = createMockServer([svc]);
104
-
105
- const result = await executeServiceMethod(server, {
106
- serviceName: "Ctx",
107
- methodName: "getClientName",
108
- params: [],
109
- http: { clientName: "my-app", authTokenPayload: undefined },
110
- });
111
-
112
- expect(result).toBe("my-app");
113
- });
114
- });