@powerhousedao/service-offering 1.0.0-dev.11 → 1.0.0-dev.12

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.
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../../document-models/subscription-instance/v1/src/reducers/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,0EAA0E,CAAC;AAetI,eAAO,MAAM,qCAAqC,EAAE,qCAsHjD,CAAC"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../../document-models/subscription-instance/v1/src/reducers/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,0EAA0E,CAAC;AAgBtI,eAAO,MAAM,qCAAqC,EAAE,qCAsHjD,CAAC"}
@@ -1,7 +1,8 @@
1
+ import { findService } from "./utils.js";
1
2
  import { AddServiceMetricServiceNotFoundError, UpdateMetricServiceNotFoundError, UpdateMetricNotFoundError, UpdateMetricUsageServiceNotFoundError, UpdateMetricUsageNotFoundError, RemoveServiceMetricServiceNotFoundError, RemoveServiceMetricNotFoundError, IncrementMetricUsageServiceNotFoundError, IncrementMetricUsageNotFoundError, DecrementMetricUsageServiceNotFoundError, DecrementMetricUsageNotFoundError, } from "../../gen/metrics/error.js";
2
3
  export const subscriptionInstanceMetricsOperations = {
3
4
  addServiceMetricOperation(state, action) {
4
- const svc = state.services.find((s) => s.id === action.input.serviceId);
5
+ const svc = findService(state, action.input.serviceId);
5
6
  if (!svc) {
6
7
  throw new AddServiceMetricServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
7
8
  }
@@ -30,7 +31,7 @@ export const subscriptionInstanceMetricsOperations = {
30
31
  });
31
32
  },
32
33
  updateMetricOperation(state, action) {
33
- const svc = state.services.find((s) => s.id === action.input.serviceId);
34
+ const svc = findService(state, action.input.serviceId);
34
35
  if (!svc) {
35
36
  throw new UpdateMetricServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
36
37
  }
@@ -50,7 +51,7 @@ export const subscriptionInstanceMetricsOperations = {
50
51
  metric.nextUsageReset = action.input.nextUsageReset || null;
51
52
  },
52
53
  updateMetricUsageOperation(state, action) {
53
- const svc = state.services.find((s) => s.id === action.input.serviceId);
54
+ const svc = findService(state, action.input.serviceId);
54
55
  if (!svc) {
55
56
  throw new UpdateMetricUsageServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
56
57
  }
@@ -61,7 +62,7 @@ export const subscriptionInstanceMetricsOperations = {
61
62
  metric.currentUsage = action.input.currentUsage;
62
63
  },
63
64
  removeServiceMetricOperation(state, action) {
64
- const svc = state.services.find((s) => s.id === action.input.serviceId);
65
+ const svc = findService(state, action.input.serviceId);
65
66
  if (!svc) {
66
67
  throw new RemoveServiceMetricServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
67
68
  }
@@ -72,7 +73,7 @@ export const subscriptionInstanceMetricsOperations = {
72
73
  svc.metrics.splice(index, 1);
73
74
  },
74
75
  incrementMetricUsageOperation(state, action) {
75
- const svc = state.services.find((s) => s.id === action.input.serviceId);
76
+ const svc = findService(state, action.input.serviceId);
76
77
  if (!svc) {
77
78
  throw new IncrementMetricUsageServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
78
79
  }
@@ -83,7 +84,7 @@ export const subscriptionInstanceMetricsOperations = {
83
84
  metric.currentUsage += action.input.incrementBy;
84
85
  },
85
86
  decrementMetricUsageOperation(state, action) {
86
- const svc = state.services.find((s) => s.id === action.input.serviceId);
87
+ const svc = findService(state, action.input.serviceId);
87
88
  if (!svc) {
88
89
  throw new DecrementMetricUsageServiceNotFoundError(`Service with ID ${action.input.serviceId} not found`);
89
90
  }
@@ -0,0 +1,7 @@
1
+ import type { SubscriptionInstanceState, Service } from "../../gen/schema/types.js";
2
+ /**
3
+ * Finds a service by ID across both top-level standalone services
4
+ * and services nested inside service groups.
5
+ */
6
+ export declare function findService(state: SubscriptionInstanceState, serviceId: string): Service | undefined;
7
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../../document-models/subscription-instance/v1/src/reducers/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,OAAO,EACR,MAAM,2BAA2B,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,yBAAyB,EAChC,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,SAAS,CAQrB"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Finds a service by ID across both top-level standalone services
3
+ * and services nested inside service groups.
4
+ */
5
+ export function findService(state, serviceId) {
6
+ const standalone = state.services.find((s) => s.id === serviceId);
7
+ if (standalone)
8
+ return standalone;
9
+ for (const group of state.serviceGroups) {
10
+ const grouped = group.services.find((s) => s.id === serviceId);
11
+ if (grouped)
12
+ return grouped;
13
+ }
14
+ return undefined;
15
+ }
@@ -1,6 +1,45 @@
1
1
  import { generateMock } from "@powerhousedao/common/utils";
2
+ import { generateId } from "document-model/core";
2
3
  import { describe, expect, it } from "vitest";
3
- import { reducer, utils, isSubscriptionInstanceDocument, addServiceMetric, updateMetric, updateMetricUsage, removeServiceMetric, incrementMetricUsage, decrementMetricUsage, AddServiceMetricInputSchema, UpdateMetricInputSchema, UpdateMetricUsageInputSchema, RemoveServiceMetricInputSchema, IncrementMetricUsageInputSchema, DecrementMetricUsageInputSchema, } from "@powerhousedao/service-offering/document-models/subscription-instance/v1";
4
+ import { reducer, utils, isSubscriptionInstanceDocument, initializeSubscription, addServiceMetric, updateMetric, updateMetricUsage, removeServiceMetric, incrementMetricUsage, decrementMetricUsage, AddServiceMetricInputSchema, UpdateMetricInputSchema, UpdateMetricUsageInputSchema, RemoveServiceMetricInputSchema, IncrementMetricUsageInputSchema, DecrementMetricUsageInputSchema, } from "@powerhousedao/service-offering/document-models/subscription-instance/v1";
5
+ /** Creates a document with a service group containing a service with a metric. */
6
+ function createDocWithServiceGroup() {
7
+ const serviceId = generateId();
8
+ const metricId = generateId();
9
+ const groupId = generateId();
10
+ let doc = utils.createDocument();
11
+ doc = reducer(doc, initializeSubscription({
12
+ createdAt: new Date().toISOString(),
13
+ customerName: "Test",
14
+ selectedBillingCycle: "MONTHLY",
15
+ globalCurrency: "USD",
16
+ autoRenew: true,
17
+ serviceGroups: [
18
+ {
19
+ id: groupId,
20
+ name: "Test Group",
21
+ optional: false,
22
+ services: [
23
+ {
24
+ id: serviceId,
25
+ name: "Test Service",
26
+ metrics: [
27
+ {
28
+ id: metricId,
29
+ name: "API Calls",
30
+ unitName: "calls",
31
+ currentUsage: 100,
32
+ freeLimit: 50,
33
+ paidLimit: 200,
34
+ },
35
+ ],
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ }));
41
+ return { doc, serviceId, metricId, groupId };
42
+ }
4
43
  describe("MetricsOperations", () => {
5
44
  it("should handle addServiceMetric operation", () => {
6
45
  const document = utils.createDocument();
@@ -62,4 +101,52 @@ describe("MetricsOperations", () => {
62
101
  expect(updatedDocument.operations.global[0].action.input).toStrictEqual(input);
63
102
  expect(updatedDocument.operations.global[0].index).toEqual(0);
64
103
  });
104
+ describe("service group metric operations", () => {
105
+ it("should increment metric usage for a service inside a service group", () => {
106
+ const { doc, serviceId, metricId } = createDocWithServiceGroup();
107
+ const updatedDoc = reducer(doc, incrementMetricUsage({
108
+ serviceId,
109
+ metricId,
110
+ incrementBy: 10,
111
+ currentTime: new Date().toISOString(),
112
+ }));
113
+ const metric = updatedDoc.state.global.serviceGroups[0].services[0].metrics[0];
114
+ expect(metric.currentUsage).toBe(110);
115
+ expect(updatedDoc.operations.global[1].error).toBeUndefined();
116
+ });
117
+ it("should decrement metric usage for a service inside a service group", () => {
118
+ const { doc, serviceId, metricId } = createDocWithServiceGroup();
119
+ const updatedDoc = reducer(doc, decrementMetricUsage({
120
+ serviceId,
121
+ metricId,
122
+ decrementBy: 25,
123
+ currentTime: new Date().toISOString(),
124
+ }));
125
+ const metric = updatedDoc.state.global.serviceGroups[0].services[0].metrics[0];
126
+ expect(metric.currentUsage).toBe(75);
127
+ expect(updatedDoc.operations.global[1].error).toBeUndefined();
128
+ });
129
+ it("should set metric usage for a service inside a service group", () => {
130
+ const { doc, serviceId, metricId } = createDocWithServiceGroup();
131
+ const updatedDoc = reducer(doc, updateMetricUsage({
132
+ serviceId,
133
+ metricId,
134
+ currentUsage: 999,
135
+ currentTime: new Date().toISOString(),
136
+ }));
137
+ const metric = updatedDoc.state.global.serviceGroups[0].services[0].metrics[0];
138
+ expect(metric.currentUsage).toBe(999);
139
+ expect(updatedDoc.operations.global[1].error).toBeUndefined();
140
+ });
141
+ it("should return error for non-existent service in service group", () => {
142
+ const { doc, metricId } = createDocWithServiceGroup();
143
+ const updatedDoc = reducer(doc, incrementMetricUsage({
144
+ serviceId: "non-existent-id",
145
+ metricId,
146
+ incrementBy: 1,
147
+ currentTime: new Date().toISOString(),
148
+ }));
149
+ expect(updatedDoc.operations.global[1].error).toBe("Service with ID non-existent-id not found");
150
+ });
151
+ });
65
152
  });
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/resources-services/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAyD/D,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAqgBxB,CAAC"}
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/resources-services/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAyD/D,eAAO,MAAM,YAAY,GACvB,UAAU,YAAY,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CA6gBxB,CAAC"}
@@ -322,25 +322,32 @@ export const getResolvers = (subgraph) => {
322
322
  }),
323
323
  ]);
324
324
  }
325
+ // create a team folder inside "Service Subscriptions" for this team's docs
326
+ const teamFolderId = generateId();
325
327
  // add reactor-level relationships so Connect syncs the child documents
326
328
  // (createEmpty guarantees CREATE_DOCUMENT is persisted before this runs)
327
329
  await reactorClient.addChildren(operatorDrive.header.id, [
328
330
  resourceInstanceDoc.header.id,
329
331
  subscriptionInstanceDoc.header.id,
330
332
  ]);
331
- // add file references to operator drive under "Service Subscriptions"
333
+ // add team folder and file references to operator drive
332
334
  await reactorClient.execute(operatorDrive.header.id, "main", [
335
+ addFolder({
336
+ id: teamFolderId,
337
+ name: teamName,
338
+ parentFolder: serviceSubscriptionsFolderId,
339
+ }),
333
340
  addFile({
334
341
  documentType: "powerhouse/resource-instance",
335
342
  id: resourceInstanceDoc.header.id,
336
343
  name: `${parsedTeamName} Resource Instance`,
337
- parentFolder: serviceSubscriptionsFolderId,
344
+ parentFolder: teamFolderId,
338
345
  }),
339
346
  addFile({
340
347
  documentType: "powerhouse/subscription-instance",
341
348
  id: subscriptionInstanceDoc.header.id,
342
349
  name: `${parsedTeamName} Subscription Instance`,
343
- parentFolder: serviceSubscriptionsFolderId,
350
+ parentFolder: teamFolderId,
344
351
  }),
345
352
  ]);
346
353
  // populate documents after all files are added to both drives
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/service-offering",
3
3
  "description": "service offering document models",
4
- "version": "1.0.0-dev.11",
4
+ "version": "1.0.0-dev.12",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",