@m5kdev/backend 0.5.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.
@@ -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 {};
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const neverthrow_1 = require("neverthrow");
4
+ const errors_1 = require("../../utils/errors");
5
+ const base_service_1 = require("./base.service");
6
+ function createUser(overrides = {}) {
7
+ return {
8
+ id: "user-1",
9
+ role: "member",
10
+ email: "user@example.com",
11
+ name: "User",
12
+ createdAt: new Date(),
13
+ updatedAt: new Date(),
14
+ banned: false,
15
+ banReason: null,
16
+ banExpires: null,
17
+ emailVerified: true,
18
+ image: null,
19
+ onboarding: null,
20
+ preferences: null,
21
+ flags: null,
22
+ stripeCustomerId: null,
23
+ paymentCustomerId: null,
24
+ paymentPlanTier: null,
25
+ paymentPlanExpiresAt: null,
26
+ ...overrides,
27
+ };
28
+ }
29
+ function createSession(overrides = {}) {
30
+ return {
31
+ id: "session-1",
32
+ createdAt: new Date(),
33
+ updatedAt: new Date(),
34
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60),
35
+ token: "token",
36
+ ipAddress: null,
37
+ userAgent: null,
38
+ userId: "user-1",
39
+ activeOrganizationId: null,
40
+ activeOrganizationRole: null,
41
+ activeTeamId: null,
42
+ activeTeamRole: null,
43
+ impersonatedBy: null,
44
+ ...overrides,
45
+ };
46
+ }
47
+ describe("BaseService procedure builder", () => {
48
+ it("runs chained steps in order and exposes state by step name", async () => {
49
+ const events = [];
50
+ class PipelineService extends base_service_1.BaseService {
51
+ run = this.procedure("run")
52
+ .use("trimmed", ({ input }) => {
53
+ events.push("trimmed");
54
+ return input.value.trim();
55
+ })
56
+ .use("upper", ({ state }) => {
57
+ events.push("upper");
58
+ return state.trimmed.toUpperCase();
59
+ })
60
+ .handle(({ state }) => {
61
+ events.push("handler");
62
+ return (0, neverthrow_1.ok)({
63
+ trimmed: state.trimmed,
64
+ upper: state.upper,
65
+ });
66
+ });
67
+ }
68
+ const service = new PipelineService();
69
+ const result = await service.run({ value: " hello " }, {});
70
+ expect(result.isOk()).toBe(true);
71
+ if (result.isOk()) {
72
+ expect(result.value).toEqual({
73
+ trimmed: "hello",
74
+ upper: "HELLO",
75
+ });
76
+ }
77
+ expect(events).toEqual(["trimmed", "upper", "handler"]);
78
+ });
79
+ it("addContextFilter wraps auth and maps query input with the default step name", async () => {
80
+ class QueryService extends base_service_1.BaseService {
81
+ run = this.procedure("run")
82
+ .addContextFilter(["user", "organization"])
83
+ .handle(({ input, state }) => (0, neverthrow_1.ok)({
84
+ input,
85
+ stateMatches: state.contextFilter === input,
86
+ }));
87
+ }
88
+ const service = new QueryService();
89
+ const unauthorized = await service.run({ search: "hello" }, {});
90
+ expect(unauthorized.isErr()).toBe(true);
91
+ if (unauthorized.isErr()) {
92
+ expect(unauthorized.error.code).toBe("UNAUTHORIZED");
93
+ }
94
+ const authorized = await service.run({ search: "hello" }, {
95
+ user: createUser(),
96
+ session: createSession({
97
+ activeOrganizationId: "org-1",
98
+ }),
99
+ });
100
+ expect(authorized.isOk()).toBe(true);
101
+ if (authorized.isOk()) {
102
+ expect(authorized.value.input.search).toBe("hello");
103
+ expect(authorized.value.stateMatches).toBe(true);
104
+ expect(authorized.value.input.filters).toEqual([
105
+ {
106
+ columnId: "userId",
107
+ type: "string",
108
+ method: "equals",
109
+ value: "user-1",
110
+ },
111
+ {
112
+ columnId: "organizationId",
113
+ type: "string",
114
+ method: "equals",
115
+ value: "org-1",
116
+ },
117
+ ]);
118
+ }
119
+ });
120
+ it("mapInput updates the input seen by later steps and the handler", async () => {
121
+ class QueryService extends base_service_1.BaseService {
122
+ run = this.procedure("run")
123
+ .requireAuth()
124
+ .mapInput("scopedQuery", ({ input, ctx }) => this.addContextFilter(ctx, { user: true, organization: true, team: true }, input, {
125
+ userId: {
126
+ columnId: "authorUserId",
127
+ method: "equals",
128
+ },
129
+ organizationId: {
130
+ columnId: "organizationId",
131
+ method: "equals",
132
+ },
133
+ teamId: {
134
+ columnId: "teamId",
135
+ method: "equals",
136
+ },
137
+ }))
138
+ .use("filterCount", ({ input }) => input.filters?.length ?? 0)
139
+ .handle(({ input, state }) => (0, neverthrow_1.ok)({
140
+ input,
141
+ filterCount: state.filterCount,
142
+ scopedInputMatches: state.scopedQuery === input,
143
+ }));
144
+ }
145
+ const service = new QueryService();
146
+ const result = await service.run({ search: "hello" }, {
147
+ user: createUser(),
148
+ session: createSession({
149
+ activeOrganizationId: "org-1",
150
+ activeTeamId: "team-1",
151
+ }),
152
+ });
153
+ expect(result.isOk()).toBe(true);
154
+ if (result.isOk()) {
155
+ expect(result.value.input.search).toBe("hello");
156
+ expect(result.value.filterCount).toBe(3);
157
+ expect(result.value.scopedInputMatches).toBe(true);
158
+ expect(result.value.input.filters).toEqual([
159
+ {
160
+ columnId: "authorUserId",
161
+ type: "string",
162
+ method: "equals",
163
+ value: "user-1",
164
+ },
165
+ {
166
+ columnId: "organizationId",
167
+ type: "string",
168
+ method: "equals",
169
+ value: "org-1",
170
+ },
171
+ {
172
+ columnId: "teamId",
173
+ type: "string",
174
+ method: "equals",
175
+ value: "team-1",
176
+ },
177
+ ]);
178
+ }
179
+ });
180
+ it("uses the base service default context type for procedures", async () => {
181
+ class ContextService extends base_service_1.BaseService {
182
+ run = this.procedure("run")
183
+ .requireAuth()
184
+ .handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.requestId}:${ctx.user.id}:${input.value}`));
185
+ }
186
+ const service = new ContextService();
187
+ const result = await service.run({ value: "ok" }, {
188
+ requestId: "req-1",
189
+ user: createUser(),
190
+ session: createSession(),
191
+ });
192
+ expect(result.isOk()).toBe(true);
193
+ if (result.isOk()) {
194
+ expect(result.value).toBe("req-1:user-1:ok");
195
+ }
196
+ });
197
+ it("requireAuth rejects missing auth context", async () => {
198
+ class ProtectedService extends base_service_1.BaseService {
199
+ run = this.procedure("run")
200
+ .requireAuth()
201
+ .handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.user.id}:${input.value}`));
202
+ }
203
+ const service = new ProtectedService();
204
+ const result = await service.run({ value: "x" }, {});
205
+ expect(result.isErr()).toBe(true);
206
+ if (result.isErr()) {
207
+ expect(result.error.code).toBe("UNAUTHORIZED");
208
+ }
209
+ });
210
+ it("requireAuth allows valid context", async () => {
211
+ class ProtectedService extends base_service_1.BaseService {
212
+ run = this.procedure("run")
213
+ .requireAuth()
214
+ .handle(({ input, ctx }) => (0, neverthrow_1.ok)(`${ctx.user.id}:${input.value}`));
215
+ }
216
+ const service = new ProtectedService();
217
+ const result = await service.run({ value: "ok" }, { user: createUser(), session: createSession() });
218
+ expect(result.isOk()).toBe(true);
219
+ if (result.isOk()) {
220
+ expect(result.value).toBe("user-1:ok");
221
+ }
222
+ });
223
+ it("normalizes thrown async errors through throwableAsync", async () => {
224
+ class ThrowingService extends base_service_1.BaseService {
225
+ run = this.procedure("run").handle(async () => {
226
+ throw new Error("boom");
227
+ });
228
+ }
229
+ const service = new ThrowingService();
230
+ const result = await service.run(undefined, {});
231
+ expect(result.isErr()).toBe(true);
232
+ if (result.isErr()) {
233
+ expect(result.error.code).toBe("INTERNAL_SERVER_ERROR");
234
+ expect(result.error.cause).toBeInstanceOf(Error);
235
+ }
236
+ });
237
+ it("logs metadata stages without input or context payloads", async () => {
238
+ class LoggedService extends base_service_1.BaseService {
239
+ run = this.procedure("run")
240
+ .requireAuth()
241
+ .handle(({ ctx }) => (0, neverthrow_1.ok)(ctx.user.id));
242
+ }
243
+ const service = new LoggedService();
244
+ const debugSpy = jest.fn();
245
+ service.logger.debug = debugSpy;
246
+ const result = await service.run({ secret: "top-secret" }, {
247
+ user: createUser(),
248
+ session: createSession(),
249
+ token: "super-token",
250
+ });
251
+ expect(result.isOk()).toBe(true);
252
+ expect(debugSpy).toHaveBeenCalledTimes(3);
253
+ const payloads = debugSpy.mock.calls.map(([payload]) => payload);
254
+ expect(payloads.map((payload) => payload.stage)).toEqual(["start", "auth_passed", "success"]);
255
+ const serializedPayloads = JSON.stringify(payloads);
256
+ expect(serializedPayloads).not.toContain("top-secret");
257
+ expect(serializedPayloads).not.toContain("super-token");
258
+ expect(payloads.every((payload) => !("input" in payload))).toBe(true);
259
+ expect(payloads.every((payload) => !("ctx" in payload))).toBe(true);
260
+ });
261
+ });
262
+ describe("BasePermissionService procedure builder", () => {
263
+ it("passes loaded entities into the handler when access is granted", async () => {
264
+ const grants = [
265
+ {
266
+ action: "read",
267
+ level: "user",
268
+ role: "member",
269
+ access: "own",
270
+ },
271
+ ];
272
+ class PermissionService extends base_service_1.BasePermissionService {
273
+ constructor() {
274
+ super({}, {}, grants);
275
+ }
276
+ run = this.procedure("run")
277
+ .access({
278
+ action: "read",
279
+ entities: ({ input }) => ({
280
+ userId: input.ownerId,
281
+ }),
282
+ })
283
+ .handle(({ state }) => (0, neverthrow_1.ok)(state.access?.userId ?? null));
284
+ }
285
+ const service = new PermissionService();
286
+ const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
287
+ expect(result.isOk()).toBe(true);
288
+ if (result.isOk()) {
289
+ expect(result.value).toBe("user-1");
290
+ }
291
+ });
292
+ it("reuses a typed state step directly when entityStep is provided", async () => {
293
+ const grants = [
294
+ {
295
+ action: "read",
296
+ level: "user",
297
+ role: "member",
298
+ access: "own",
299
+ },
300
+ ];
301
+ class PermissionService extends base_service_1.BasePermissionService {
302
+ constructor() {
303
+ super({}, {}, grants);
304
+ }
305
+ run = this.procedure("run")
306
+ .use("record", ({ input }) => (0, neverthrow_1.ok)({
307
+ id: "resource-1",
308
+ userId: input.ownerId,
309
+ teamId: null,
310
+ organizationId: null,
311
+ }))
312
+ .access({
313
+ action: "read",
314
+ entityStep: "record",
315
+ })
316
+ .handle(({ state }) => (0, neverthrow_1.ok)(state.access === state.record));
317
+ }
318
+ const service = new PermissionService();
319
+ const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
320
+ expect(result.isOk()).toBe(true);
321
+ if (result.isOk()) {
322
+ expect(result.value).toBe(true);
323
+ }
324
+ });
325
+ it("returns FORBIDDEN when access validation fails", async () => {
326
+ const grants = [
327
+ {
328
+ action: "read",
329
+ level: "user",
330
+ role: "member",
331
+ access: "own",
332
+ },
333
+ ];
334
+ class PermissionService extends base_service_1.BasePermissionService {
335
+ constructor() {
336
+ super({}, {}, grants);
337
+ }
338
+ run = this.procedure("run")
339
+ .access({
340
+ action: "read",
341
+ entities: ({ input }) => ({
342
+ userId: input.ownerId,
343
+ }),
344
+ })
345
+ .handle(() => (0, neverthrow_1.ok)("allowed"));
346
+ }
347
+ const service = new PermissionService();
348
+ const result = await service.run({ ownerId: "other-user" }, { user: createUser(), session: createSession() });
349
+ expect(result.isErr()).toBe(true);
350
+ if (result.isErr()) {
351
+ expect(result.error.code).toBe("FORBIDDEN");
352
+ }
353
+ });
354
+ it("propagates entity loader failures through access", async () => {
355
+ const grants = [
356
+ {
357
+ action: "read",
358
+ level: "user",
359
+ role: "member",
360
+ access: "own",
361
+ },
362
+ ];
363
+ class PermissionService extends base_service_1.BasePermissionService {
364
+ constructor() {
365
+ super({}, {}, grants);
366
+ }
367
+ run = this.procedure("run")
368
+ .access({
369
+ action: "read",
370
+ entities: async () => (0, neverthrow_1.err)(new errors_1.ServerError({
371
+ code: "NOT_FOUND",
372
+ layer: "service",
373
+ layerName: "PermissionService",
374
+ message: "Missing entity",
375
+ })),
376
+ })
377
+ .handle(() => (0, neverthrow_1.ok)("allowed"));
378
+ }
379
+ const service = new PermissionService();
380
+ const result = await service.run({ ownerId: "user-1" }, { user: createUser(), session: createSession() });
381
+ expect(result.isErr()).toBe(true);
382
+ if (result.isErr()) {
383
+ expect(result.error.code).toBe("INTERNAL_SERVER_ERROR");
384
+ }
385
+ });
386
+ it("skips entity loading when grants already allow all access", async () => {
387
+ const grants = [
388
+ {
389
+ action: "read",
390
+ level: "user",
391
+ role: "member",
392
+ access: "all",
393
+ },
394
+ ];
395
+ const resolveEntity = jest.fn(async () => ({ userId: "user-1" }));
396
+ class PermissionService extends base_service_1.BasePermissionService {
397
+ constructor() {
398
+ super({}, {}, grants);
399
+ }
400
+ run = this.procedure("run")
401
+ .access({
402
+ action: "read",
403
+ entities: resolveEntity,
404
+ })
405
+ .handle(() => (0, neverthrow_1.ok)("allowed"));
406
+ }
407
+ const service = new PermissionService();
408
+ const result = await service.run({ ownerId: "other-user" }, { user: createUser(), session: createSession() });
409
+ expect(result.isOk()).toBe(true);
410
+ if (result.isOk()) {
411
+ expect(result.value).toBe("allowed");
412
+ }
413
+ expect(resolveEntity).not.toHaveBeenCalled();
414
+ });
415
+ });
@@ -389,22 +389,22 @@ export declare class ConnectRepository extends BaseTableRepository<Orm, Schema,
389
389
  updatedAt: Date | null;
390
390
  }[]>>;
391
391
  upsert(data: ConnectInsert, tx?: Orm): Promise<import("../base/base.dto").ServerResult<{
392
- provider: string;
393
392
  id: string;
394
393
  createdAt: Date;
395
394
  updatedAt: Date | null;
396
- userId: string;
397
395
  expiresAt: Date | null;
396
+ userId: string;
397
+ parentId: string | null;
398
398
  accessToken: string;
399
399
  refreshToken: string | null;
400
400
  scope: string | null;
401
+ provider: string;
401
402
  accountType: string;
402
403
  providerAccountId: string;
403
404
  handle: string | null;
404
405
  displayName: string | null;
405
406
  avatarUrl: string | null;
406
407
  tokenType: string | null;
407
- parentId: string | null;
408
408
  metadataJson: unknown;
409
409
  revokedAt: Date | null;
410
410
  lastRefreshedAt: Date | null;
@@ -16,43 +16,43 @@ export declare class ConnectService extends BaseService<{
16
16
  url: string;
17
17
  }>;
18
18
  handleCallback(user: User, sessionId: string, providerId: string, code: string, state: string): Promise<import("../base/base.dto").ServerResult<{
19
- provider: string;
20
19
  id: string;
21
20
  createdAt: Date;
22
21
  updatedAt: Date | null;
23
- userId: string;
24
22
  expiresAt: Date | null;
23
+ userId: string;
24
+ parentId: string | null;
25
25
  accessToken: string;
26
26
  refreshToken: string | null;
27
27
  scope: string | null;
28
+ provider: string;
28
29
  accountType: string;
29
30
  providerAccountId: string;
30
31
  handle: string | null;
31
32
  displayName: string | null;
32
33
  avatarUrl: string | null;
33
34
  tokenType: string | null;
34
- parentId: string | null;
35
35
  metadataJson: unknown;
36
36
  revokedAt: Date | null;
37
37
  lastRefreshedAt: Date | null;
38
38
  }>>;
39
39
  refreshToken(connectionId: string): Promise<import("../base/base.dto").ServerResult<{
40
- provider: string;
41
40
  id: string;
42
41
  createdAt: Date;
43
42
  updatedAt: Date | null;
44
- userId: string;
45
43
  expiresAt: Date | null;
44
+ userId: string;
45
+ parentId: string | null;
46
46
  accessToken: string;
47
47
  refreshToken: string | null;
48
48
  scope: string | null;
49
+ provider: string;
49
50
  accountType: string;
50
51
  providerAccountId: string;
51
52
  handle: string | null;
52
53
  displayName: string | null;
53
54
  avatarUrl: string | null;
54
55
  tokenType: string | null;
55
- parentId: string | null;
56
56
  metadataJson: unknown;
57
57
  revokedAt: Date | null;
58
58
  lastRefreshedAt: Date | null;
@@ -12,20 +12,20 @@ export declare function createConnectTRPC({ router, privateProcedure: procedure
12
12
  inactive?: boolean | undefined;
13
13
  };
14
14
  output: {
15
- provider: string;
16
15
  id: string;
17
16
  createdAt: Date;
18
17
  userId: string;
18
+ provider: string;
19
19
  accountType: string;
20
20
  providerAccountId: string;
21
21
  updatedAt?: Date | null | undefined;
22
22
  expiresAt?: Date | null | undefined;
23
+ parentId?: string | null | undefined;
23
24
  scope?: string | null | undefined;
24
25
  handle?: string | null | undefined;
25
26
  displayName?: string | null | undefined;
26
27
  avatarUrl?: string | null | undefined;
27
28
  tokenType?: string | null | undefined;
28
- parentId?: string | null | undefined;
29
29
  metadataJson?: unknown;
30
30
  revokedAt?: Date | null | undefined;
31
31
  lastRefreshedAt?: Date | null | undefined;
@@ -1,20 +1,41 @@
1
1
  import type { CreateRecurrenceSchema, DeleteRecurrenceRulesSchema, DeleteRecurrenceSchema, UpdateRecurrenceRulesSchema, UpdateRecurrenceSchema } from "@m5kdev/commons/modules/recurrence/recurrence.schema";
2
- import type { QueryInput } from "@m5kdev/commons/modules/schemas/query.schema";
3
- import type { Context, User } from "../auth/auth.lib";
2
+ import type { Context } from "../auth/auth.lib";
4
3
  import type { ServerResultAsync } from "../base/base.dto";
5
4
  import { BaseService } from "../base/base.service";
6
5
  import type { CreateWithRulesResult, RecurrenceRepository, RecurrenceRulesRepository } from "./recurrence.repository";
7
6
  export declare class RecurrenceService extends BaseService<{
8
7
  recurrence: RecurrenceRepository;
9
8
  recurrenceRules: RecurrenceRulesRepository;
10
- }, Record<string, never>> {
11
- create(data: CreateRecurrenceSchema, ctx: Context): ServerResultAsync<CreateWithRulesResult>;
12
- list(query?: QueryInput, ctx?: {
13
- user?: User;
14
- }): ServerResultAsync<{
15
- rows: CreateWithRulesResult["recurrence"][];
9
+ }, Record<string, never>, Context> {
10
+ readonly list: import("../base/base.procedure").ServiceProcedure<{
11
+ page?: number | undefined;
12
+ limit?: number | undefined;
13
+ sort?: string | undefined;
14
+ order?: "asc" | "desc" | undefined;
15
+ filters?: {
16
+ columnId: string;
17
+ type: "string" | "number" | "boolean" | "date" | "enum";
18
+ method: "on" | "contains" | "equals" | "starts_with" | "ends_with" | "greater_than" | "less_than" | "between" | "before" | "after" | "oneOf" | "intersect" | "isEmpty" | "isNotEmpty" | "is_null" | "is_not_null";
19
+ value: string | number | boolean | string[];
20
+ valueTo?: string | undefined;
21
+ endColumnId?: string | undefined;
22
+ }[] | undefined;
23
+ }, Context, {
24
+ rows: {
25
+ id: string;
26
+ name: string | null;
27
+ createdAt: Date;
28
+ updatedAt: Date;
29
+ metadata: Record<string, any> | null;
30
+ userId: string | null;
31
+ teamId: string | null;
32
+ organizationId: string | null;
33
+ enabled: boolean;
34
+ kind: string | null;
35
+ }[];
16
36
  total: number;
17
37
  }>;
38
+ create(data: CreateRecurrenceSchema, ctx: Context): ServerResultAsync<CreateWithRulesResult>;
18
39
  findById(id: string): ServerResultAsync<CreateWithRulesResult["recurrence"] | null>;
19
40
  update(data: UpdateRecurrenceSchema & {
20
41
  id: string;
@@ -31,6 +31,9 @@ function mapRuleToInsert(rule) {
31
31
  return out;
32
32
  }
33
33
  class RecurrenceService extends base_service_1.BaseService {
34
+ list = this.procedure("list")
35
+ .addContextFilter(["user"])
36
+ .handle(({ input }) => this.repository.recurrence.queryList(input));
34
37
  async create(data, ctx) {
35
38
  const recurrenceData = {
36
39
  name: data.name,
@@ -44,10 +47,6 @@ class RecurrenceService extends base_service_1.BaseService {
44
47
  const rulesData = data.recurrenceRules.map(mapRuleToInsert);
45
48
  return this.repository.recurrence.createWithRules(recurrenceData, rulesData);
46
49
  }
47
- async list(query, ctx) {
48
- const queryWithUser = ctx?.user ? this.addUserFilter(ctx.user.id, query, "userId") : query;
49
- return this.repository.recurrence.queryList(queryWithUser);
50
- }
51
50
  async findById(id) {
52
51
  const result = await this.repository.recurrence.findById(id);
53
52
  if (result.isErr())