@m5kdev/backend 0.4.0 → 0.6.0

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 (38) hide show
  1. package/dist/src/modules/ai/ai.prompts.d.ts +5 -0
  2. package/dist/src/modules/ai/ai.prompts.js +16 -0
  3. package/dist/src/modules/ai/ai.service.d.ts +26 -12
  4. package/dist/src/modules/ai/ai.service.js +105 -15
  5. package/dist/src/modules/auth/auth.dto.d.ts +2 -2
  6. package/dist/src/modules/auth/auth.lib.d.ts +3 -3
  7. package/dist/src/modules/auth/auth.repository.d.ts +2 -2
  8. package/dist/src/modules/auth/auth.repository.js +1 -1
  9. package/dist/src/modules/auth/auth.service.d.ts +3 -3
  10. package/dist/src/modules/auth/auth.service.js +1 -1
  11. package/dist/src/modules/auth/auth.trpc.d.ts +6 -6
  12. package/dist/src/modules/auth/auth.trpc.js +1 -1
  13. package/dist/src/modules/base/base.abstract.d.ts +3 -2
  14. package/dist/src/modules/base/base.abstract.js +10 -1
  15. package/dist/src/modules/base/base.procedure.d.ts +112 -0
  16. package/dist/src/modules/base/base.procedure.js +289 -0
  17. package/dist/src/modules/base/base.repository.d.ts +1 -0
  18. package/dist/src/modules/base/base.repository.js +12 -2
  19. package/dist/src/modules/base/base.service.d.ts +17 -5
  20. package/dist/src/modules/base/base.service.js +7 -0
  21. package/dist/src/modules/base/base.service.test.d.ts +1 -0
  22. package/dist/src/modules/base/base.service.test.js +415 -0
  23. package/dist/src/modules/connect/connect.repository.d.ts +3 -3
  24. package/dist/src/modules/connect/connect.service.d.ts +4 -4
  25. package/dist/src/modules/connect/connect.trpc.d.ts +2 -2
  26. package/dist/src/modules/recurrence/recurrence.service.d.ts +29 -8
  27. package/dist/src/modules/recurrence/recurrence.service.js +3 -4
  28. package/dist/src/modules/recurrence/recurrence.trpc.d.ts +3 -3
  29. package/dist/src/modules/recurrence/recurrence.trpc.js +1 -1
  30. package/dist/src/modules/tag/tag.db.js +1 -1
  31. package/dist/src/modules/tag/tag.repository.js +27 -26
  32. package/dist/src/modules/tag/tag.service.d.ts +86 -15
  33. package/dist/src/modules/tag/tag.service.js +20 -12
  34. package/dist/src/modules/tag/tag.trpc.d.ts +3 -3
  35. package/dist/src/types.d.ts +5 -5
  36. package/dist/src/utils/trpc.d.ts +6 -6
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +6 -5
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServiceProcedureBuilder = createServiceProcedureBuilder;
4
+ exports.createPermissionServiceProcedureBuilder = createPermissionServiceProcedureBuilder;
5
+ const neverthrow_1 = require("neverthrow");
6
+ const DEFAULT_CONTEXT_FILTER_INCLUDE = [
7
+ "user",
8
+ ];
9
+ function isServerResult(value) {
10
+ return (typeof value === "object" &&
11
+ value !== null &&
12
+ "isErr" in value &&
13
+ typeof value.isErr === "function" &&
14
+ "isOk" in value &&
15
+ typeof value.isOk === "function");
16
+ }
17
+ async function normalizeProcedureResult(result) {
18
+ const resolved = await result;
19
+ return isServerResult(resolved) ? resolved : (0, neverthrow_1.ok)(resolved);
20
+ }
21
+ function assertUniqueStepName(steps, stepName) {
22
+ if (steps.some((step) => step.stepName === stepName)) {
23
+ throw new Error(`Duplicate service procedure step name: ${stepName}`);
24
+ }
25
+ }
26
+ function hasStepName(steps, stepName) {
27
+ return steps.some((step) => step.stepName === stepName);
28
+ }
29
+ function getContextFilterInclude(include = DEFAULT_CONTEXT_FILTER_INCLUDE) {
30
+ return {
31
+ user: include.includes("user"),
32
+ organization: include.includes("organization"),
33
+ team: include.includes("team"),
34
+ };
35
+ }
36
+ function getFailureStage(code) {
37
+ return code === "FORBIDDEN" || code === "UNAUTHORIZED" ? "forbidden" : "error";
38
+ }
39
+ function logProcedureStage(host, procedureName, ctx, stage, { stepName, durationMs, errorCode, } = {}) {
40
+ host.logger.debug({
41
+ procedureName,
42
+ stage,
43
+ stepName,
44
+ durationMs,
45
+ errorCode,
46
+ hasUser: Boolean(ctx.user),
47
+ hasSession: Boolean(ctx.session),
48
+ });
49
+ }
50
+ function createRequireAuthStep(host) {
51
+ return {
52
+ stage: "auth",
53
+ stepName: "auth",
54
+ run: async ({ ctx }) => {
55
+ if (!ctx.user || !ctx.session) {
56
+ return host.error("UNAUTHORIZED", "Unauthorized");
57
+ }
58
+ return (0, neverthrow_1.ok)(true);
59
+ },
60
+ };
61
+ }
62
+ function createUseStep(stepName, step) {
63
+ return {
64
+ stage: "use",
65
+ stepName,
66
+ run: async (args) => normalizeProcedureResult(step(args)),
67
+ };
68
+ }
69
+ function createInputStep(stepName, step) {
70
+ return {
71
+ stage: "input",
72
+ stepName,
73
+ run: async (args) => normalizeProcedureResult(step(args)),
74
+ };
75
+ }
76
+ function createContextFilterStep(host, include) {
77
+ const contextInclude = getContextFilterInclude(include);
78
+ return {
79
+ stage: "input",
80
+ stepName: "contextFilter",
81
+ run: async ({ input, ctx }) => (0, neverthrow_1.ok)(host.addContextFilter(ctx, contextInclude, input)),
82
+ };
83
+ }
84
+ function createAccessStep(host, config) {
85
+ return {
86
+ stage: "access",
87
+ stepName: "access",
88
+ run: async (args) => {
89
+ const typedArgs = args;
90
+ if (!typedArgs.ctx.user || !typedArgs.ctx.session) {
91
+ return host.error("UNAUTHORIZED", "Unauthorized");
92
+ }
93
+ const permissionContext = {
94
+ user: typedArgs.ctx.user,
95
+ session: typedArgs.ctx.session,
96
+ };
97
+ if ("entityStep" in config && typeof config.entityStep === "string") {
98
+ const entities = typedArgs.state[config.entityStep];
99
+ const hasPermission = host.checkPermission(permissionContext, config.action, entities, config.grants);
100
+ if (!hasPermission) {
101
+ return host.error("FORBIDDEN");
102
+ }
103
+ return (0, neverthrow_1.ok)(entities);
104
+ }
105
+ if (typeof config.entities === "function") {
106
+ const resolveEntities = config.entities;
107
+ let loadedEntities;
108
+ const permission = await host.checkPermissionAsync(permissionContext, config.action, async () => {
109
+ const entityResult = await normalizeProcedureResult(resolveEntities(typedArgs));
110
+ if (entityResult.isOk()) {
111
+ loadedEntities = entityResult.value;
112
+ }
113
+ return entityResult;
114
+ }, config.grants);
115
+ if (permission.isErr()) {
116
+ return permission;
117
+ }
118
+ if (!permission.value) {
119
+ return host.error("FORBIDDEN");
120
+ }
121
+ return (0, neverthrow_1.ok)(loadedEntities);
122
+ }
123
+ const entities = config.entities;
124
+ const hasPermission = host.checkPermission(permissionContext, config.action, entities, config.grants);
125
+ if (!hasPermission) {
126
+ return host.error("FORBIDDEN");
127
+ }
128
+ return (0, neverthrow_1.ok)(entities);
129
+ },
130
+ };
131
+ }
132
+ function createProcedureHandler(host, config, handler) {
133
+ return async (input, ctx) => host.throwableAsync(async () => {
134
+ const state = {};
135
+ const startTime = Date.now();
136
+ const typedCtx = ctx;
137
+ let currentInput = input;
138
+ logProcedureStage(host, config.name, typedCtx, "start");
139
+ try {
140
+ for (const step of config.steps) {
141
+ const stepResult = await step.run({
142
+ input: currentInput,
143
+ ctx: typedCtx,
144
+ state,
145
+ repository: host.repository,
146
+ service: host.service,
147
+ logger: host.logger,
148
+ });
149
+ if (stepResult.isErr()) {
150
+ logProcedureStage(host, config.name, typedCtx, getFailureStage(stepResult.error.code), {
151
+ stepName: step.stepName,
152
+ durationMs: Date.now() - startTime,
153
+ errorCode: stepResult.error.code,
154
+ });
155
+ return stepResult;
156
+ }
157
+ state[step.stepName] = stepResult.value;
158
+ if (step.stage === "input") {
159
+ currentInput = stepResult.value;
160
+ }
161
+ if (step.stage === "auth") {
162
+ logProcedureStage(host, config.name, typedCtx, "auth_passed", {
163
+ stepName: step.stepName,
164
+ });
165
+ }
166
+ if (step.stage === "access") {
167
+ logProcedureStage(host, config.name, typedCtx, "access_passed", {
168
+ stepName: step.stepName,
169
+ });
170
+ }
171
+ }
172
+ const handlerResult = await normalizeProcedureResult(handler({
173
+ input: currentInput,
174
+ ctx: ctx,
175
+ state: state,
176
+ repository: host.repository,
177
+ service: host.service,
178
+ logger: host.logger,
179
+ }));
180
+ if (handlerResult.isErr()) {
181
+ logProcedureStage(host, config.name, typedCtx, getFailureStage(handlerResult.error.code), {
182
+ durationMs: Date.now() - startTime,
183
+ errorCode: handlerResult.error.code,
184
+ });
185
+ return handlerResult;
186
+ }
187
+ logProcedureStage(host, config.name, typedCtx, "success", {
188
+ durationMs: Date.now() - startTime,
189
+ });
190
+ return handlerResult;
191
+ }
192
+ catch (error) {
193
+ const serverError = host.handleUnknownError(error);
194
+ logProcedureStage(host, config.name, typedCtx, getFailureStage(serverError.code), {
195
+ durationMs: Date.now() - startTime,
196
+ errorCode: serverError.code,
197
+ });
198
+ throw error;
199
+ }
200
+ });
201
+ }
202
+ function createServiceProcedureBuilder(host, config) {
203
+ function addContextFilter(include) {
204
+ const steps = hasStepName(config.steps, "auth")
205
+ ? config.steps
206
+ : [...config.steps, createRequireAuthStep(host)];
207
+ assertUniqueStepName(steps, "contextFilter");
208
+ return createServiceProcedureBuilder(host, {
209
+ ...config,
210
+ steps: [...steps, createContextFilterStep(host, include)],
211
+ });
212
+ }
213
+ const builder = {
214
+ use(stepName, step) {
215
+ assertUniqueStepName(config.steps, stepName);
216
+ return createServiceProcedureBuilder(host, {
217
+ ...config,
218
+ steps: [...config.steps, createUseStep(stepName, step)],
219
+ });
220
+ },
221
+ mapInput(stepName, step) {
222
+ assertUniqueStepName(config.steps, stepName);
223
+ return createServiceProcedureBuilder(host, {
224
+ ...config,
225
+ steps: [...config.steps, createInputStep(stepName, step)],
226
+ });
227
+ },
228
+ addContextFilter,
229
+ requireAuth() {
230
+ assertUniqueStepName(config.steps, "auth");
231
+ return createServiceProcedureBuilder(host, {
232
+ ...config,
233
+ steps: [...config.steps, createRequireAuthStep(host)],
234
+ });
235
+ },
236
+ handle(handler) {
237
+ return createProcedureHandler(host, config, handler);
238
+ },
239
+ };
240
+ return builder;
241
+ }
242
+ function createPermissionServiceProcedureBuilder(host, config) {
243
+ function addContextFilter(include) {
244
+ const steps = hasStepName(config.steps, "auth")
245
+ ? config.steps
246
+ : [...config.steps, createRequireAuthStep(host)];
247
+ assertUniqueStepName(steps, "contextFilter");
248
+ return createPermissionServiceProcedureBuilder(host, {
249
+ ...config,
250
+ steps: [...steps, createContextFilterStep(host, include)],
251
+ });
252
+ }
253
+ function access(accessConfig) {
254
+ assertUniqueStepName(config.steps, "access");
255
+ return createPermissionServiceProcedureBuilder(host, {
256
+ ...config,
257
+ steps: [...config.steps, createAccessStep(host, accessConfig)],
258
+ });
259
+ }
260
+ const builder = {
261
+ use(stepName, step) {
262
+ assertUniqueStepName(config.steps, stepName);
263
+ return createPermissionServiceProcedureBuilder(host, {
264
+ ...config,
265
+ steps: [...config.steps, createUseStep(stepName, step)],
266
+ });
267
+ },
268
+ mapInput(stepName, step) {
269
+ assertUniqueStepName(config.steps, stepName);
270
+ return createPermissionServiceProcedureBuilder(host, {
271
+ ...config,
272
+ steps: [...config.steps, createInputStep(stepName, step)],
273
+ });
274
+ },
275
+ addContextFilter,
276
+ requireAuth() {
277
+ assertUniqueStepName(config.steps, "auth");
278
+ return createPermissionServiceProcedureBuilder(host, {
279
+ ...config,
280
+ steps: [...config.steps, createRequireAuthStep(host)],
281
+ });
282
+ },
283
+ access,
284
+ handle(handler) {
285
+ return createProcedureHandler(host, config, handler);
286
+ },
287
+ };
288
+ return builder;
289
+ }
@@ -32,6 +32,7 @@ export declare class BaseRepository<O extends LibSQLDatabase<any>, S extends Rec
32
32
  getConditionBuilder(): ConditionBuilder;
33
33
  getConditionBuilder(table: undefined): ConditionBuilder;
34
34
  getConditionBuilder<TTable extends SQLiteTableWithColumns<any>>(table: TTable): TableConditionBuilder<TTable>;
35
+ throwableQuery<T>(fn: () => Promise<T>): ServerResultAsync<T>;
35
36
  withPagination<TQuery>(query: TQuery, { page, limit }: Pick<QueryInput, "page" | "limit">): TQuery;
36
37
  withSorting<TTable extends SQLiteTableWithColumns<any>, TQuery>(query: TQuery, { sort, order }: Pick<QueryInput, "sort" | "order">, table?: TTable): TQuery;
37
38
  withSortingAndPagination<TTable extends SQLiteTableWithColumns<any>, TQuery>(query: TQuery, { sort, order, page, limit }: Pick<QueryInput, "sort" | "order" | "page" | "limit">, table?: TTable): TQuery;
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseExternaRepository = exports.BaseTableRepository = exports.BaseRepository = exports.arrayContains = exports.TableConditionBuilder = exports.ConditionBuilder = void 0;
4
4
  const drizzle_orm_1 = require("drizzle-orm");
5
5
  const neverthrow_1 = require("neverthrow");
6
- const base_abstract_1 = require("./base.abstract");
7
- const base_dto_1 = require("./base.dto");
6
+ const errors_1 = require("../../utils/errors");
8
7
  const applyPagination_1 = require("../utils/applyPagination");
9
8
  const applySorting_1 = require("../utils/applySorting");
10
9
  const getConditionsFromFilters_1 = require("../utils/getConditionsFromFilters");
10
+ const base_abstract_1 = require("./base.abstract");
11
+ const base_dto_1 = require("./base.dto");
11
12
  class ConditionBuilder {
12
13
  conditions;
13
14
  constructor(conditions = []) {
@@ -66,6 +67,15 @@ class BaseRepository extends base_abstract_1.Base {
66
67
  }
67
68
  return new TableConditionBuilder(table);
68
69
  }
70
+ throwableQuery(fn) {
71
+ return this.throwablePromise(() => fn(), (error) => new errors_1.ServerError({
72
+ code: "INTERNAL_SERVER_ERROR",
73
+ layer: "repository",
74
+ layerName: this.constructor.name,
75
+ message: "Database query failed",
76
+ cause: error,
77
+ }));
78
+ }
69
79
  withPagination(query, { page, limit }) {
70
80
  return (0, applyPagination_1.applyPagination)(query, limit, page);
71
81
  }
@@ -3,22 +3,33 @@ import type { Context, Session, User } from "../auth/auth.lib";
3
3
  import { Base } from "./base.abstract";
4
4
  import type { ServerResult, ServerResultAsync } from "./base.dto";
5
5
  import { type Entity, type ResourceActionGrant, type ResourceGrant } from "./base.grants";
6
- import type { BaseExternaRepository, BaseRepository } from "./base.repository";
7
- export declare class BaseService<Repositories extends Record<string, BaseRepository<any, any, any> | BaseExternaRepository>, Services extends Record<string, BaseService<any, any>>> extends Base {
6
+ import { type PermissionServiceProcedureBuilder, type ServiceProcedureBuilder, type ServiceProcedureContext } from "./base.procedure";
7
+ export type { PermissionServiceProcedureBuilder, ServiceProcedure, ServiceProcedureAccessConfig, ServiceProcedureAccessEntitiesConfig, ServiceProcedureAccessStateConfig, ServiceProcedureArgs, ServiceProcedureBuilder, ServiceProcedureContext, ServiceProcedureContextFilteredInput, ServiceProcedureContextFilterScope, ServiceProcedureEntityStepName, ServiceProcedureInputMapper, } from "./base.procedure";
8
+ export declare class BaseService<Repositories extends Record<string, Base>, Services extends Record<string, Base>, DefaultContext extends ServiceProcedureContext = ServiceProcedureContext> extends Base {
8
9
  repository: Repositories;
9
10
  service: Services;
10
11
  constructor(repository?: Repositories, service?: Services);
11
- addUserFilter(value: string, query?: QueryInput, columnId?: string, method?: QueryFilter["method"]): QueryInput;
12
+ addUserFilter(value: string, query?: undefined, columnId?: string, method?: QueryFilter["method"]): QueryInput;
13
+ addUserFilter<TQuery extends QueryInput>(value: string, query: TQuery, columnId?: string, method?: QueryFilter["method"]): TQuery;
14
+ protected procedure<TInput, TCtx extends ServiceProcedureContext = DefaultContext>(name: string): ServiceProcedureBuilder<TInput, TCtx, Repositories, Services>;
12
15
  addContextFilter(ctx: Context, include?: {
13
16
  user?: boolean;
14
17
  organization?: boolean;
15
18
  team?: boolean;
16
- }, query?: QueryInput, map?: Record<string, {
19
+ }, query?: undefined, map?: Record<string, {
17
20
  columnId: string;
18
21
  method: QueryFilter["method"];
19
22
  }>): QueryInput;
23
+ addContextFilter<TQuery extends QueryInput>(ctx: Context, include: {
24
+ user?: boolean;
25
+ organization?: boolean;
26
+ team?: boolean;
27
+ } | undefined, query: TQuery, map?: Record<string, {
28
+ columnId: string;
29
+ method: QueryFilter["method"];
30
+ }>): TQuery;
20
31
  }
21
- export declare class BasePermissionService<Repositories extends Record<string, BaseRepository<any, any, any> | BaseExternaRepository>, Services extends Record<string, BaseService<any, any>>> extends BaseService<Repositories, Services> {
32
+ export declare class BasePermissionService<Repositories extends Record<string, Base>, Services extends Record<string, Base>, DefaultContext extends ServiceProcedureContext = ServiceProcedureContext> extends BaseService<Repositories, Services, DefaultContext> {
22
33
  grants: ResourceGrant[];
23
34
  constructor(repository: Repositories, service: Services, grants?: ResourceGrant[]);
24
35
  accessGuard<T extends Entity>(ctx: {
@@ -29,6 +40,7 @@ export declare class BasePermissionService<Repositories extends Record<string, B
29
40
  session: Session;
30
41
  user: User;
31
42
  }, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<true>;
43
+ protected procedure<TInput, TCtx extends ServiceProcedureContext = DefaultContext>(name: string): PermissionServiceProcedureBuilder<TInput, TCtx, Repositories, Services>;
32
44
  checkPermission<T extends Entity>(ctx: {
33
45
  session: Session;
34
46
  user: User;
@@ -4,6 +4,7 @@ exports.BasePermissionService = exports.BaseService = void 0;
4
4
  const neverthrow_1 = require("neverthrow");
5
5
  const base_abstract_1 = require("./base.abstract");
6
6
  const base_grants_1 = require("./base.grants");
7
+ const base_procedure_1 = require("./base.procedure");
7
8
  class BaseService extends base_abstract_1.Base {
8
9
  repository;
9
10
  service;
@@ -25,6 +26,9 @@ class BaseService extends base_abstract_1.Base {
25
26
  ? { ...query, filters: [...(query?.filters ?? []), userFilter] }
26
27
  : { filters: [userFilter] };
27
28
  }
29
+ procedure(name) {
30
+ return (0, base_procedure_1.createServiceProcedureBuilder)(this, { name, steps: [] });
31
+ }
28
32
  addContextFilter(ctx, include = {
29
33
  user: true,
30
34
  organization: false,
@@ -92,6 +96,9 @@ class BasePermissionService extends BaseService {
92
96
  return this.error("FORBIDDEN");
93
97
  return (0, neverthrow_1.ok)(true);
94
98
  }
99
+ procedure(name) {
100
+ return (0, base_procedure_1.createPermissionServiceProcedureBuilder)(this, { name, steps: [] });
101
+ }
95
102
  checkPermission(ctx, action, entities, grants) {
96
103
  const actionGrants = grants ?? this.grants.filter((grant) => grant.action === action);
97
104
  return (0, base_grants_1.checkPermissionSync)(ctx, actionGrants, entities);
@@ -0,0 +1 @@
1
+ export {};