@managemint-solutions/sdk 0.24.2 → 0.26.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.
package/README.md CHANGED
@@ -38,12 +38,19 @@ const supabase = createSupabaseClient({
38
38
 
39
39
  // Inputs and outputs are the shared types from @managemint-solutions/entities.
40
40
  const statuses: StatusEntity[] = await supabase.statuses.list(StatusEntityTypes.PROJECT);
41
+ // `is_complete` marks the finished state and is optional — it defaults to false on create and
42
+ // is left unchanged on update when omitted. `is_fail` and `is_triage` are read back on every
43
+ // status but are not settable through the SDK yet.
41
44
  const created = await supabase.statuses.create({
42
45
  label: 'In progress',
43
46
  colour: '#2ecc71',
44
47
  entity: StatusEntityTypes.PROJECT,
45
48
  });
46
- await supabase.statuses.update(created.mms_id, { label: 'Doing', colour: '#2ecc71' });
49
+ await supabase.statuses.update(created.mms_id, {
50
+ label: 'Done',
51
+ colour: '#2ecc71',
52
+ is_complete: true,
53
+ });
47
54
  await supabase.statuses.remove(created.mms_id);
48
55
  ```
49
56
 
@@ -137,7 +144,9 @@ const page = await supabase.projects.list({
137
144
  client_id: clientId, // a UUID, or the base64-JSON option value a select sends
138
145
  });
139
146
  // `search` matches name/specification; `created_*`, `start_date_*` and `end_date_*` bound the
140
- // date columns, and `status_id_in` filters by status.
147
+ // date columns, and `status_id_in` filters by status. `status_id_not_in` is the exclusion form —
148
+ // it keeps rows with no status, which `status_id_in` cannot express. `completed` is carried but
149
+ // not acted on here: the api resolves it to status ids against the statuses catalogue first.
141
150
 
142
151
  const project = await supabase.projects.create({
143
152
  name: 'Website rebuild',
@@ -171,7 +180,9 @@ const page = await supabase.tasks.list({
171
180
  priority_in: [1, 3],
172
181
  });
173
182
  // `search` matches name/description, and `client_id`, `assigned_to_id`, the date ranges and
174
- // `status_id_in` narrow it further.
183
+ // `status_id_in` narrow it further. `status_id_not_in` excludes statuses while keeping rows with
184
+ // no status; `completed` is carried but not acted on here — the api resolves it to status ids
185
+ // against the statuses catalogue first.
175
186
 
176
187
  const task = await supabase.tasks.create({
177
188
  name: 'Draft the proposal',
@@ -31,7 +31,10 @@ const CLIENT_LIST_SELECT = `
31
31
  status:statuses!status_id (
32
32
  mms_id,
33
33
  colour,
34
- label
34
+ label,
35
+ is_complete,
36
+ is_fail,
37
+ is_triage
35
38
  ),
36
39
  created_by:user_profiles!created_by (*)
37
40
  `;
@@ -44,7 +47,10 @@ const CLIENT_DETAIL_SELECT = `
44
47
  status:statuses!status_id (
45
48
  mms_id,
46
49
  label,
47
- colour
50
+ colour,
51
+ is_complete,
52
+ is_fail,
53
+ is_triage
48
54
  ),
49
55
  contract_client,
50
56
  contracted_hours,
@@ -1,5 +1,5 @@
1
1
  import type { CreateProjectDto as CreateProjectDtoType, GetProjectsDto as GetProjectsDtoType, ProjectIdDto as ProjectIdDtoType, UpdateProjectDto as UpdateProjectDtoType } from '@managemint-solutions/entities/projects/dto';
2
- export declare class GetProjectsDto implements Omit<GetProjectsDtoType, 'status_id_in'> {
2
+ export declare class GetProjectsDto implements Omit<GetProjectsDtoType, 'status_id_in' | 'status_id_not_in'> {
3
3
  readonly search?: string | null;
4
4
  readonly page: number;
5
5
  readonly page_size: number;
@@ -10,6 +10,8 @@ export declare class GetProjectsDto implements Omit<GetProjectsDtoType, 'status_
10
10
  readonly end_date_after?: string | null;
11
11
  readonly end_date_before?: string | null;
12
12
  readonly status_id_in?: string[] | null;
13
+ readonly status_id_not_in?: string[] | null;
14
+ readonly completed?: boolean | null;
13
15
  readonly client_id?: string | null;
14
16
  readonly archive: boolean;
15
17
  }
@@ -16,9 +16,10 @@ const transforms_1 = require("../transforms");
16
16
  const validators_1 = require("../validators");
17
17
  // class-validator versions of the shared DTO types from @managemint-solutions/entities, so the
18
18
  // wire contract cannot drift.
19
- // `status_id_in` is omitted from the implemented type on purpose: the entities type carries the
20
- // CSV wire value (`string | null`), while this class carries the `@Transform`-parsed
21
- // `string[] | null` the query builder feeds to `.in('status_id', ...)`.
19
+ // `status_id_in` and `status_id_not_in` are omitted from the implemented type on purpose: the
20
+ // entities type carries the CSV wire values (`string | null`), while this class carries the
21
+ // `@Transform`-parsed `string[] | null` the query builder feeds to `.in('status_id', ...)` and to
22
+ // the `status_id.is.null,status_id.not.in.(...)` exclusion.
22
23
  class GetProjectsDto {
23
24
  search;
24
25
  page = 1;
@@ -30,6 +31,10 @@ class GetProjectsDto {
30
31
  end_date_after;
31
32
  end_date_before;
32
33
  status_id_in;
34
+ status_id_not_in;
35
+ // Tri-state: true = only completed rows, false = only rows that are not completed, omitted = no
36
+ // completion filter. The api resolves it to status ids before it reaches `list`.
37
+ completed;
33
38
  client_id;
34
39
  archive = false;
35
40
  }
@@ -99,6 +104,20 @@ __decorate([
99
104
  (0, class_validator_1.IsUUID)('7', { each: true, message: 'Each status ID must be a valid UUID v7' }),
100
105
  __metadata("design:type", Object)
101
106
  ], GetProjectsDto.prototype, "status_id_in", void 0);
107
+ __decorate([
108
+ (0, class_validator_1.IsOptional)(),
109
+ (0, validators_1.IsNullable)(),
110
+ (0, class_transformer_1.Transform)(({ value }) => value ? value.split(',').map((id) => id.trim()) : null, { toClassOnly: true }),
111
+ (0, class_validator_1.IsUUID)('7', { each: true, message: 'Each status ID must be a valid UUID v7' }),
112
+ __metadata("design:type", Object)
113
+ ], GetProjectsDto.prototype, "status_id_not_in", void 0);
114
+ __decorate([
115
+ (0, class_validator_1.IsOptional)(),
116
+ (0, validators_1.IsNullable)(),
117
+ (0, class_transformer_1.Transform)(({ value }) => value === 'true' ? true : value === 'false' ? false : value, { toClassOnly: true }),
118
+ (0, class_validator_1.IsBoolean)({ message: 'Completed must be true or false' }),
119
+ __metadata("design:type", Object)
120
+ ], GetProjectsDto.prototype, "completed", void 0);
102
121
  __decorate([
103
122
  (0, class_validator_1.IsOptional)(),
104
123
  (0, validators_1.IsNullable)(),
@@ -32,7 +32,10 @@ const PROJECT_LIST_SELECT = `
32
32
  status:statuses!status_id (
33
33
  mms_id,
34
34
  colour,
35
- label
35
+ label,
36
+ is_complete,
37
+ is_fail,
38
+ is_triage
36
39
  ),
37
40
  created_by:user_profiles!created_by (*),
38
41
  manager:user_profiles!manager_id (*),
@@ -46,7 +49,10 @@ const PROJECT_DETAIL_SELECT = `
46
49
  status:statuses!status_id (
47
50
  mms_id,
48
51
  label,
49
- colour
52
+ colour,
53
+ is_complete,
54
+ is_fail,
55
+ is_triage
50
56
  ),
51
57
  has_budget,
52
58
  start_date,
@@ -118,6 +124,15 @@ class ProjectsResource {
118
124
  if (query.status_id_in && query.status_id_in.length > 0) {
119
125
  projectsQuery = projectsQuery.in('status_id', query.status_id_in);
120
126
  }
127
+ // Excluding statuses has to be spelled out as "no status OR not one of these": PostgREST can
128
+ // only filter a parent row by an embedded column with `!inner`, which would drop projects that
129
+ // have no status at all — and a project with no status is not complete, so it belongs in the
130
+ // default view. SQL's `NOT IN` drops nulls for the same reason, hence the explicit null arm.
131
+ // The ids are `@IsUUID('7')`-validated by the DTO, so interpolating them is safe. Repeated
132
+ // `or` params are ANDed by PostgREST, so this composes with the search filter above.
133
+ if (query.status_id_not_in && query.status_id_not_in.length > 0) {
134
+ projectsQuery = projectsQuery.or(`status_id.is.null,status_id.not.in.(${query.status_id_not_in.join(',')})`);
135
+ }
121
136
  if (query.client_id) {
122
137
  projectsQuery = projectsQuery.eq('client_id', query.client_id);
123
138
  }
@@ -10,8 +10,10 @@ export declare class AddStatusDto implements AddStatusDtoType {
10
10
  readonly label: string;
11
11
  readonly colour: string;
12
12
  readonly entity: StatusEntityTypes;
13
+ readonly is_complete?: boolean;
13
14
  }
14
15
  export declare class UpdateStatusDto implements UpdateStatusDtoType {
15
16
  readonly label: string;
16
17
  readonly colour: string;
18
+ readonly is_complete?: boolean;
17
19
  }
@@ -36,6 +36,8 @@ class AddStatusDto {
36
36
  colour;
37
37
  // `entity` is the wire name; the SDK maps it to the `entity_type` column.
38
38
  entity;
39
+ // Marks the status as the finished state; omitted means false.
40
+ is_complete;
39
41
  }
40
42
  exports.AddStatusDto = AddStatusDto;
41
43
  __decorate([
@@ -55,10 +57,17 @@ __decorate([
55
57
  (0, class_validator_1.IsEnum)(enum_1.StatusEntityTypes, { message: 'Invalid entity type' }),
56
58
  __metadata("design:type", String)
57
59
  ], AddStatusDto.prototype, "entity", void 0);
60
+ __decorate([
61
+ (0, class_validator_1.IsOptional)(),
62
+ (0, class_validator_1.IsBoolean)({ message: 'is_complete must be true or false' }),
63
+ __metadata("design:type", Boolean)
64
+ ], AddStatusDto.prototype, "is_complete", void 0);
58
65
  class UpdateStatusDto {
59
66
  label;
60
67
  // Colour must be a valid hex code (e.g., #FFFFFF)
61
68
  colour;
69
+ // Marks the status as the finished state; omitted leaves the flag unchanged.
70
+ is_complete;
62
71
  }
63
72
  exports.UpdateStatusDto = UpdateStatusDto;
64
73
  __decorate([
@@ -74,3 +83,8 @@ __decorate([
74
83
  (0, class_validator_1.Matches)(/^#[0-9A-Fa-f]{6}$/, { message: 'Colour must be a valid hex code' }),
75
84
  __metadata("design:type", String)
76
85
  ], UpdateStatusDto.prototype, "colour", void 0);
86
+ __decorate([
87
+ (0, class_validator_1.IsOptional)(),
88
+ (0, class_validator_1.IsBoolean)({ message: 'is_complete must be true or false' }),
89
+ __metadata("design:type", Boolean)
90
+ ], UpdateStatusDto.prototype, "is_complete", void 0);
@@ -22,9 +22,16 @@ __exportStar(require("./dto"), exports);
22
22
  // The public contract is the shared StatusEntity from @managemint-solutions/entities; the
23
23
  // `as StatusEntity` casts declare the shape the select string below produces (the columns are
24
24
  // defined in the api's supabase/migrations).
25
- const STATUS_SELECT = 'mms_id, label, colour';
25
+ const STATUS_SELECT = 'mms_id, label, colour, is_complete, is_fail, is_triage';
26
26
  // The audited write returns the whole row; the wire shape is the STATUS_SELECT columns.
27
- const toStatus = (row) => ({ mms_id: row.mms_id, label: row.label, colour: row.colour });
27
+ const toStatus = (row) => ({
28
+ mms_id: row.mms_id,
29
+ label: row.label,
30
+ colour: row.colour,
31
+ is_complete: row.is_complete,
32
+ is_fail: row.is_fail,
33
+ is_triage: row.is_triage,
34
+ });
28
35
  class StatusesResource {
29
36
  supabase;
30
37
  auth;
@@ -52,6 +59,7 @@ class StatusesResource {
52
59
  label: input.label,
53
60
  colour: input.colour,
54
61
  entity_type: input.entity,
62
+ is_complete: input.is_complete ?? false,
55
63
  organization_id: this.auth.organization_id,
56
64
  created_by: this.auth.mms_id,
57
65
  });
@@ -61,6 +69,7 @@ class StatusesResource {
61
69
  const row = await this.table.update(statusId, {
62
70
  label: input.label,
63
71
  colour: input.colour,
72
+ ...(input.is_complete !== undefined && { is_complete: input.is_complete }),
64
73
  updated_at: new Date().toISOString(),
65
74
  last_updated_by: this.auth.mms_id,
66
75
  });
@@ -2,7 +2,7 @@ import type { CreateTaskDto as CreateTaskDtoType, GetTasksDto as GetTasksDtoType
2
2
  export declare class TaskIdDto implements TaskIdDtoType {
3
3
  readonly taskId: string;
4
4
  }
5
- export declare class GetTasksDto implements Omit<GetTasksDtoType, 'status_id_in' | 'priority_in'> {
5
+ export declare class GetTasksDto implements Omit<GetTasksDtoType, 'status_id_in' | 'status_id_not_in' | 'priority_in'> {
6
6
  readonly search?: string | null;
7
7
  readonly page: number;
8
8
  readonly page_size: number;
@@ -16,6 +16,8 @@ export declare class GetTasksDto implements Omit<GetTasksDtoType, 'status_id_in'
16
16
  readonly end_date_after?: string | null;
17
17
  readonly end_date_before?: string | null;
18
18
  readonly status_id_in?: string[] | null;
19
+ readonly status_id_not_in?: string[] | null;
20
+ readonly completed?: boolean | null;
19
21
  readonly priority_in?: number[] | null;
20
22
  readonly archive: boolean;
21
23
  }
package/dist/tasks/dto.js CHANGED
@@ -25,9 +25,10 @@ __decorate([
25
25
  ,
26
26
  __metadata("design:type", String)
27
27
  ], TaskIdDto.prototype, "taskId", void 0);
28
- // `status_id_in` and `priority_in` are omitted from the implemented type on purpose: the entities
29
- // type carries the CSV wire values, while this class carries the `@Transform`-parsed arrays the
30
- // query builder feeds to `.in('status_id', ...)` / `.in('priority', ...)`.
28
+ // `status_id_in`, `status_id_not_in` and `priority_in` are omitted from the implemented type on
29
+ // purpose: the entities type carries the CSV wire values, while this class carries the
30
+ // `@Transform`-parsed arrays the query builder feeds to `.in('status_id', ...)` /
31
+ // `.in('priority', ...)` and to the `status_id.is.null,status_id.not.in.(...)` exclusion.
31
32
  class GetTasksDto {
32
33
  search;
33
34
  page = 1;
@@ -42,6 +43,10 @@ class GetTasksDto {
42
43
  end_date_after;
43
44
  end_date_before;
44
45
  status_id_in;
46
+ status_id_not_in;
47
+ // Tri-state: true = only completed rows, false = only rows that are not completed, omitted = no
48
+ // completion filter. The api resolves it to status ids before it reaches `list`.
49
+ completed;
45
50
  priority_in;
46
51
  archive = false;
47
52
  }
@@ -135,6 +140,20 @@ __decorate([
135
140
  (0, class_validator_1.IsUUID)('7', { each: true, message: 'Invalid status ID format' }),
136
141
  __metadata("design:type", Object)
137
142
  ], GetTasksDto.prototype, "status_id_in", void 0);
143
+ __decorate([
144
+ (0, class_validator_1.IsOptional)(),
145
+ (0, validators_1.IsNullable)(),
146
+ (0, class_transformer_1.Transform)(({ value }) => value ? value.split(',').map((id) => id.trim()) : null, { toClassOnly: true }),
147
+ (0, class_validator_1.IsUUID)('7', { each: true, message: 'Invalid status ID format' }),
148
+ __metadata("design:type", Object)
149
+ ], GetTasksDto.prototype, "status_id_not_in", void 0);
150
+ __decorate([
151
+ (0, class_validator_1.IsOptional)(),
152
+ (0, validators_1.IsNullable)(),
153
+ (0, class_transformer_1.Transform)(({ value }) => value === 'true' ? true : value === 'false' ? false : value, { toClassOnly: true }),
154
+ (0, class_validator_1.IsBoolean)({ message: 'Completed must be true or false' }),
155
+ __metadata("design:type", Object)
156
+ ], GetTasksDto.prototype, "completed", void 0);
138
157
  __decorate([
139
158
  (0, class_validator_1.IsOptional)(),
140
159
  (0, validators_1.IsNullable)(),
@@ -31,7 +31,10 @@ const TASK_LIST_SELECT = `
31
31
  status:statuses!status_id (
32
32
  mms_id,
33
33
  colour,
34
- label
34
+ label,
35
+ is_complete,
36
+ is_fail,
37
+ is_triage
35
38
  ),
36
39
  priority,
37
40
  assigned_to:user_profiles!assigned_to_id (*)
@@ -49,7 +52,10 @@ const TASK_DETAIL_SELECT = `
49
52
  status:statuses!status_id (
50
53
  mms_id,
51
54
  colour,
52
- label
55
+ label,
56
+ is_complete,
57
+ is_fail,
58
+ is_triage
53
59
  ),
54
60
  priority,
55
61
  assigned_to:user_profiles!assigned_to_id (*),
@@ -127,6 +133,15 @@ class TasksResource {
127
133
  if (query.status_id_in && query.status_id_in.length > 0) {
128
134
  tasksQuery = tasksQuery.in('status_id', query.status_id_in);
129
135
  }
136
+ // Excluding statuses has to be spelled out as "no status OR not one of these": PostgREST can
137
+ // only filter a parent row by an embedded column with `!inner`, which would drop tasks that
138
+ // have no status at all — and a task with no status is not complete, so it belongs in the
139
+ // default view. SQL's `NOT IN` drops nulls for the same reason, hence the explicit null arm.
140
+ // The ids are `@IsUUID('7')`-validated by the DTO, so interpolating them is safe. Repeated
141
+ // `or` params are ANDed by PostgREST, so this composes with the search filter above.
142
+ if (query.status_id_not_in && query.status_id_not_in.length > 0) {
143
+ tasksQuery = tasksQuery.or(`status_id.is.null,status_id.not.in.(${query.status_id_not_in.join(',')})`);
144
+ }
130
145
  if (query.priority_in && query.priority_in.length > 0) {
131
146
  tasksQuery = tasksQuery.in('priority', query.priority_in);
132
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@managemint-solutions/sdk",
3
- "version": "0.24.2",
3
+ "version": "0.26.0",
4
4
  "description": "Typed Supabase data-access SDK for ManageMint Solutions",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Scott Bebington <scottbebington@gmail.com>",
@@ -47,7 +47,7 @@
47
47
  "testEnvironment": "node"
48
48
  },
49
49
  "dependencies": {
50
- "@managemint-solutions/entities": "^1.8.0"
50
+ "@managemint-solutions/entities": "^1.10.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@nestjs/common": "^11.0.0",