@slothmoney/agent-cli 0.2.0 → 0.3.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/dist/contracts.js CHANGED
@@ -38,12 +38,18 @@ function validateAssignment(value, index) {
38
38
  const assignment = requireObject(value, label);
39
39
  rejectUnknownFields(assignment, new Set([
40
40
  'transactionRef',
41
+ 'assignmentScope',
41
42
  'categoryId',
42
43
  'lineItemId',
43
44
  'categorySplits',
44
45
  'incomeSubtype',
45
46
  ]), label);
46
47
  requireString(assignment.transactionRef, `${label}.transactionRef`);
48
+ if (assignment.assignmentScope !== undefined
49
+ && assignment.assignmentScope !== 'personal'
50
+ && assignment.assignmentScope !== 'joint') {
51
+ throw new UsageError(`${label}.assignmentScope must be personal or joint`);
52
+ }
47
53
  if (assignment.categoryId !== undefined && assignment.categoryId !== null) {
48
54
  requireString(assignment.categoryId, `${label}.categoryId`);
49
55
  }
@@ -74,6 +80,9 @@ function validateAssignment(value, index) {
74
80
  }
75
81
  return {
76
82
  transactionRef: assignment.transactionRef,
83
+ ...(assignment.assignmentScope !== undefined
84
+ ? { assignmentScope: assignment.assignmentScope }
85
+ : {}),
77
86
  ...(assignment.categoryId !== undefined ? { categoryId: assignment.categoryId } : {}),
78
87
  ...(assignment.lineItemId !== undefined ? { lineItemId: assignment.lineItemId } : {}),
79
88
  ...(categorySplits !== undefined ? { categorySplits } : {}),
@@ -130,6 +139,20 @@ function isTransaction(value) {
130
139
  && (value.categoryId === null || typeof value.categoryId === 'string')
131
140
  && (value.lineItemId === null || typeof value.lineItemId === 'string')
132
141
  && Array.isArray(value.categorySplits)
142
+ && (value.jointBudgetContribution === null
143
+ || (isObject(value.jointBudgetContribution)
144
+ && typeof value.jointBudgetContribution.eligible === 'boolean'
145
+ && typeof value.jointBudgetContribution.included === 'boolean'
146
+ && Number.isInteger(value.jointBudgetContribution.amountPence)
147
+ && Number(value.jointBudgetContribution.amountPence) > 0
148
+ && (value.jointBudgetContribution.categoryId === null
149
+ || typeof value.jointBudgetContribution.categoryId === 'string')
150
+ && (value.jointBudgetContribution.lineItemId === null
151
+ || typeof value.jointBudgetContribution.lineItemId === 'string')
152
+ && Array.isArray(value.jointBudgetContribution.categorySplits)
153
+ && (value.jointBudgetContribution.incomeSubtype === null
154
+ || value.jointBudgetContribution.incomeSubtype === 'pay'
155
+ || value.jointBudgetContribution.incomeSubtype === 'interest')))
133
156
  && (value.incomeSubtype === null
134
157
  || value.incomeSubtype === 'pay'
135
158
  || value.incomeSubtype === 'interest'));
@@ -143,12 +166,28 @@ function isTransactionsResponse(value) {
143
166
  function isAssignmentResponse(value) {
144
167
  return (isObject(value)
145
168
  && Array.isArray(value.succeeded)
146
- && value.succeeded.every((item) => isObject(item) && typeof item.transactionRef === 'string')
169
+ && value.succeeded.every((item) => (isObject(item)
170
+ && typeof item.transactionRef === 'string'
171
+ && (item.assignmentScope === 'personal' || item.assignmentScope === 'joint')))
147
172
  && Array.isArray(value.failed)
148
173
  && value.failed.every((item) => (isObject(item)
149
174
  && typeof item.error === 'string'
150
175
  && (item.transactionRef === undefined || typeof item.transactionRef === 'string'))));
151
176
  }
177
+ export function validateJointBudgetSettingsResponse(value) {
178
+ if (!isObject(value)
179
+ || Object.keys(value).some((key) => !new Set([
180
+ 'includeSharedPersonalTransactions',
181
+ 'updatedAt',
182
+ 'updatedBy',
183
+ ]).has(key))
184
+ || typeof value.includeSharedPersonalTransactions !== 'boolean'
185
+ || (value.updatedAt !== null && !isIsoDateTime(value.updatedAt))
186
+ || (value.updatedBy !== null && typeof value.updatedBy !== 'string')) {
187
+ throw new ApiError('Invalid joint budget settings response from the Agent API');
188
+ }
189
+ return value;
190
+ }
152
191
  function isHttpUrl(value) {
153
192
  if (typeof value !== 'string')
154
193
  return false;
@@ -175,6 +214,55 @@ function isPartnerResponse(value) {
175
214
  && isIsoDateTime(value.expiresAt)
176
215
  && value.status === 'open');
177
216
  }
217
+ function hasOnlyFields(value, fields) {
218
+ const allowed = new Set(fields);
219
+ return Object.keys(value).every((key) => allowed.has(key));
220
+ }
221
+ function isGoal(value) {
222
+ return (isObject(value)
223
+ && hasOnlyFields(value, [
224
+ 'id',
225
+ 'name',
226
+ 'targetAmount',
227
+ 'targetMonthKey',
228
+ 'isAchieved',
229
+ 'sharedWithPartner',
230
+ ])
231
+ && typeof value.id === 'string'
232
+ && value.id.trim().length > 0
233
+ && typeof value.name === 'string'
234
+ && value.name.trim().length > 0
235
+ && (value.targetAmount === null
236
+ || (typeof value.targetAmount === 'number' && Number.isFinite(value.targetAmount)))
237
+ && (value.targetMonthKey === null
238
+ || (typeof value.targetMonthKey === 'string'
239
+ && /^\d{4}-(0[1-9]|1[0-2])$/.test(value.targetMonthKey)))
240
+ && typeof value.isAchieved === 'boolean'
241
+ && typeof value.sharedWithPartner === 'boolean');
242
+ }
243
+ function isCurrency(value) {
244
+ return typeof value === 'string' && /^[A-Z]{3}$/.test(value);
245
+ }
246
+ function isGoalsResponse(value) {
247
+ return (isObject(value)
248
+ && hasOnlyFields(value, ['currency', 'goals'])
249
+ && isCurrency(value.currency)
250
+ && Array.isArray(value.goals)
251
+ && value.goals.every(isGoal));
252
+ }
253
+ function isGoalMutationResponse(value) {
254
+ return (isObject(value)
255
+ && hasOnlyFields(value, ['currency', 'goal'])
256
+ && isCurrency(value.currency)
257
+ && isGoal(value.goal));
258
+ }
259
+ function isGoalDeleteResponse(value) {
260
+ return (isObject(value)
261
+ && hasOnlyFields(value, ['deleted', 'deletedGoalId'])
262
+ && value.deleted === true
263
+ && typeof value.deletedGoalId === 'string'
264
+ && value.deletedGoalId.trim().length > 0);
265
+ }
178
266
  export function parseApiResponse(command, value) {
179
267
  const valid = command === 'categories'
180
268
  ? isCategoryResponse(value)
@@ -182,7 +270,13 @@ export function parseApiResponse(command, value) {
182
270
  ? isTransactionsResponse(value)
183
271
  : command === 'assign'
184
272
  ? isAssignmentResponse(value)
185
- : isPartnerResponse(value);
273
+ : command === 'ask-partner'
274
+ ? isPartnerResponse(value)
275
+ : command === 'goals-list'
276
+ ? isGoalsResponse(value)
277
+ : command === 'goals-delete'
278
+ ? isGoalDeleteResponse(value)
279
+ : isGoalMutationResponse(value);
186
280
  if (!valid) {
187
281
  const label = command === 'assign' ? 'assignment' : command;
188
282
  throw new ApiError(`Invalid ${label} response from the Agent API`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slothmoney/agent-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Command-line access to the Sloth Money Agent API.",
5
5
  "type": "module",
6
6
  "bin": {