monorise 1.0.0 → 1.1.0-dev.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 (99) hide show
  1. package/dist/base/index.d.ts +84 -3
  2. package/dist/base/index.js.map +1 -1
  3. package/dist/cli/cli.js +6 -0
  4. package/dist/cli/cli.js.map +1 -1
  5. package/dist/core/chunk-QV4Q5377.js +76 -0
  6. package/dist/core/chunk-QV4Q5377.js.map +1 -0
  7. package/dist/core/index.d.ts +182 -35
  8. package/dist/core/index.js +1486 -94
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/service.config-ZJEZ6EKA.js +13 -0
  11. package/dist/core/service.config-ZJEZ6EKA.js.map +1 -0
  12. package/dist/proxy/index.d.ts +35 -0
  13. package/dist/proxy/index.js +75 -0
  14. package/dist/proxy/index.js.map +1 -0
  15. package/dist/react/actions/websocket.action.d.ts +71 -0
  16. package/dist/react/actions/websocket.action.d.ts.map +1 -0
  17. package/dist/react/chunk-4D22OCZG.js +65 -0
  18. package/dist/react/chunk-4D22OCZG.js.map +1 -0
  19. package/dist/react/chunk-4N3P4ONH.js +588 -0
  20. package/dist/react/chunk-4N3P4ONH.js.map +1 -0
  21. package/dist/react/chunk-4Y4KWGJD.js +182 -0
  22. package/dist/react/chunk-4Y4KWGJD.js.map +1 -0
  23. package/dist/react/chunk-757E5UYA.js +893 -0
  24. package/dist/react/chunk-757E5UYA.js.map +1 -0
  25. package/dist/react/chunk-A5TI2FW3.js +13 -0
  26. package/dist/react/chunk-A5TI2FW3.js.map +1 -0
  27. package/dist/react/chunk-B3XDGUFO.js +489 -0
  28. package/dist/react/chunk-B3XDGUFO.js.map +1 -0
  29. package/dist/react/chunk-BPBCUO2Z.js +248 -0
  30. package/dist/react/chunk-BPBCUO2Z.js.map +1 -0
  31. package/dist/react/chunk-CQBOIXWK.js +142 -0
  32. package/dist/react/chunk-CQBOIXWK.js.map +1 -0
  33. package/dist/react/chunk-DRH2BB7I.js +383 -0
  34. package/dist/react/chunk-DRH2BB7I.js.map +1 -0
  35. package/dist/react/chunk-EQ3PKQ2S.js +402 -0
  36. package/dist/react/chunk-EQ3PKQ2S.js.map +1 -0
  37. package/dist/react/chunk-H64MMAL7.js +245 -0
  38. package/dist/react/chunk-H64MMAL7.js.map +1 -0
  39. package/dist/react/chunk-KJX5LOMN.js +43 -0
  40. package/dist/react/chunk-KJX5LOMN.js.map +1 -0
  41. package/dist/react/chunk-MO35V2Y7.js +172 -0
  42. package/dist/react/chunk-MO35V2Y7.js.map +1 -0
  43. package/dist/react/chunk-UC3E72G7.js +73 -0
  44. package/dist/react/chunk-UC3E72G7.js.map +1 -0
  45. package/dist/react/chunk-UHMKB3OR.js +5568 -0
  46. package/dist/react/chunk-UHMKB3OR.js.map +1 -0
  47. package/dist/react/chunk-UQPQBWEQ.js +54 -0
  48. package/dist/react/chunk-UQPQBWEQ.js.map +1 -0
  49. package/dist/react/chunk-XCDCVRJR.js +43 -0
  50. package/dist/react/chunk-XCDCVRJR.js.map +1 -0
  51. package/dist/react/chunk-XOYAZDIH.js +47 -0
  52. package/dist/react/chunk-XOYAZDIH.js.map +1 -0
  53. package/dist/react/chunk-YNFQEPO5.js +29 -0
  54. package/dist/react/chunk-YNFQEPO5.js.map +1 -0
  55. package/dist/react/dist-es-35AO47NO.js +90 -0
  56. package/dist/react/dist-es-35AO47NO.js.map +1 -0
  57. package/dist/react/dist-es-5GDBXNKQ.js +333 -0
  58. package/dist/react/dist-es-5GDBXNKQ.js.map +1 -0
  59. package/dist/react/dist-es-B3JDGWY6.js +71 -0
  60. package/dist/react/dist-es-B3JDGWY6.js.map +1 -0
  61. package/dist/react/dist-es-IWIE5JLA.js +169 -0
  62. package/dist/react/dist-es-IWIE5JLA.js.map +1 -0
  63. package/dist/react/dist-es-NRIS3TYJ.js +494 -0
  64. package/dist/react/dist-es-NRIS3TYJ.js.map +1 -0
  65. package/dist/react/dist-es-VCXAEYYN.js +22 -0
  66. package/dist/react/dist-es-VCXAEYYN.js.map +1 -0
  67. package/dist/react/dist-es-VU33JFTZ.js +379 -0
  68. package/dist/react/dist-es-VU33JFTZ.js.map +1 -0
  69. package/dist/react/event-streams-OSOTOTTP.js +277 -0
  70. package/dist/react/event-streams-OSOTOTTP.js.map +1 -0
  71. package/dist/react/index.d.ts +53 -4
  72. package/dist/react/index.d.ts.map +1 -1
  73. package/dist/react/index.js +10948 -190
  74. package/dist/react/index.js.map +1 -1
  75. package/dist/react/loadSso-ME7MKAM3.js +556 -0
  76. package/dist/react/loadSso-ME7MKAM3.js.map +1 -0
  77. package/dist/react/service.config-ZJEZ6EKA-FC2TR3GH.js +14 -0
  78. package/dist/react/service.config-ZJEZ6EKA-FC2TR3GH.js.map +1 -0
  79. package/dist/react/services/core.service.d.ts +11 -1
  80. package/dist/react/services/core.service.d.ts.map +1 -1
  81. package/dist/react/signin-LOXYIE5I.js +653 -0
  82. package/dist/react/signin-LOXYIE5I.js.map +1 -0
  83. package/dist/react/sso-oidc-X63KRRLO.js +786 -0
  84. package/dist/react/sso-oidc-X63KRRLO.js.map +1 -0
  85. package/dist/react/sts-OXBEY7HY.js +3948 -0
  86. package/dist/react/sts-OXBEY7HY.js.map +1 -0
  87. package/dist/react/websocket/WebSocketManager.d.ts +68 -0
  88. package/dist/react/websocket/WebSocketManager.d.ts.map +1 -0
  89. package/dist/react/websocket/index.d.ts +3 -0
  90. package/dist/react/websocket/index.d.ts.map +1 -0
  91. package/dist/react/websocket/optimistic.d.ts +51 -0
  92. package/dist/react/websocket/optimistic.d.ts.map +1 -0
  93. package/dist/react/websocket-QHA7SQXG.js +10 -0
  94. package/dist/react/websocket-QHA7SQXG.js.map +1 -0
  95. package/dist/sst/components/monorise-core.d.ts +10 -0
  96. package/dist/sst/components/monorise-core.d.ts.map +1 -1
  97. package/dist/sst/index.js +75 -12
  98. package/dist/sst/index.js.map +1 -1
  99. package/package.json +9 -1
@@ -1,58 +1,14 @@
1
- var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- }
17
- return a;
18
- };
19
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
- var __objRest = (source, exclude) => {
21
- var target = {};
22
- for (var prop in source)
23
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
- target[prop] = source[prop];
25
- if (source != null && __getOwnPropSymbols)
26
- for (var prop of __getOwnPropSymbols(source)) {
27
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
- target[prop] = source[prop];
29
- }
30
- return target;
31
- };
32
- var __export = (target, all) => {
33
- for (var name in all)
34
- __defProp(target, name, { get: all[name], enumerable: true });
35
- };
36
- var __async = (__this, __arguments, generator) => {
37
- return new Promise((resolve, reject) => {
38
- var fulfilled = (value) => {
39
- try {
40
- step(generator.next(value));
41
- } catch (e) {
42
- reject(e);
43
- }
44
- };
45
- var rejected = (value) => {
46
- try {
47
- step(generator.throw(value));
48
- } catch (e) {
49
- reject(e);
50
- }
51
- };
52
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
53
- step((generator = generator.apply(__this, __arguments)).next());
54
- });
55
- };
1
+ import {
2
+ CORE_EVENT_BUS,
3
+ CORE_TABLE,
4
+ ENTITY_REPLICATION_INDEX,
5
+ MUTUAL_REPLICATION_INDEX,
6
+ __async,
7
+ __export,
8
+ __objRest,
9
+ __spreadProps,
10
+ __spreadValues
11
+ } from "./chunk-QV4Q5377.js";
56
12
 
57
13
  // controllers/setupRoutes.ts
58
14
  import { Hono } from "hono";
@@ -140,6 +96,11 @@ var setupCommonRoutes = (container) => {
140
96
  container.deleteEntityController.controller
141
97
  );
142
98
  app.get("/tag/:entityType/:tagName", container.listTagsController.controller);
99
+ app.post("/transaction", container.executeTransactionController.controller);
100
+ app.post(
101
+ "/ws/ticket/:entityType/:entityId",
102
+ container.createTicketController.controller
103
+ );
143
104
  return app;
144
105
  };
145
106
 
@@ -175,6 +136,7 @@ var StandardErrorCode = {
175
136
  ENTITY_ID_IS_UNDEFINED: "ENTITY_ID_IS_UNDEFINED",
176
137
  ENTITY_IS_UNDEFINED: "ENTITY_IS_UNDEFINED",
177
138
  ENTITY_NOT_FOUND: "ENTITY_NOT_FOUND",
139
+ INVALID_CONDITION: "INVALID_CONDITION",
178
140
  INVALID_ENTITY_TYPE: "INVALID_ENTITY_TYPE",
179
141
  INVALID_MUTUAL: "INVALID_MUTUAL",
180
142
  INVALID_QUERY: "INVALID_QUERY",
@@ -187,7 +149,10 @@ var StandardErrorCode = {
187
149
  REPLICATION_ERROR: "REPLICATION_ERROR",
188
150
  RETRYABLE_MUTUAL_LOCK_CONFLICT: "RETRYABLE_MUTUAL_LOCK_CONFLICT",
189
151
  TAG_IS_UNDEFINED: "TAG_IS_UNDEFINED",
152
+ TRANSACTION_EMPTY: "TRANSACTION_EMPTY",
190
153
  TRANSACTION_FAILED: "TRANSACTION_FAILED",
154
+ TRANSACTION_ITEM_LIMIT_EXCEEDED: "TRANSACTION_ITEM_LIMIT_EXCEEDED",
155
+ TRANSACTION_UNIQUE_FIELD_UPDATE: "TRANSACTION_UNIQUE_FIELD_UPDATE",
191
156
  UNIQUE_VALUE_EXISTS: "UNIQUE_VALUE_EXISTS"
192
157
  };
193
158
 
@@ -832,22 +797,22 @@ var EntityRepository = class extends Repository {
832
797
  }
833
798
  });
834
799
  }
835
- adjustEntity(entityType, entityId, adjustments, constraints) {
800
+ adjustEntity(entityType, entityId, adjustments, opts) {
836
801
  return __async(this, null, function* () {
837
802
  const entity = new Entity(entityType, entityId);
838
- const { UpdateExpression, ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues } = this.toAdjustUpdate(adjustments, constraints);
803
+ const { UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues } = this.toAdjustUpdate(adjustments);
839
804
  const updatedAtExpression = ", #updatedAt = :updatedAt";
840
805
  ExpressionAttributeNames["#updatedAt"] = "updatedAt";
841
806
  ExpressionAttributeValues[":updatedAt"] = { S: (/* @__PURE__ */ new Date()).toISOString() };
842
- const resp = yield this.dynamodbClient.updateItem(__spreadProps(__spreadValues({
807
+ const resp = yield this.dynamodbClient.updateItem({
843
808
  TableName: this.TABLE_NAME,
844
809
  Key: entity.keys(),
845
- UpdateExpression: UpdateExpression + updatedAtExpression
846
- }, ConditionExpression && { ConditionExpression }), {
847
- ExpressionAttributeNames,
848
- ExpressionAttributeValues,
810
+ UpdateExpression: UpdateExpression + updatedAtExpression,
811
+ ConditionExpression: (opts == null ? void 0 : opts.ConditionExpression) || "attribute_exists(PK)",
812
+ ExpressionAttributeNames: __spreadValues(__spreadValues({}, ExpressionAttributeNames), opts == null ? void 0 : opts.ExpressionAttributeNames),
813
+ ExpressionAttributeValues: __spreadValues(__spreadValues({}, ExpressionAttributeValues), opts == null ? void 0 : opts.ExpressionAttributeValues),
849
814
  ReturnValues: "ALL_NEW"
850
- }));
815
+ });
851
816
  return Entity.fromItem(resp.Attributes);
852
817
  });
853
818
  }
@@ -1857,6 +1822,217 @@ var TagRepository = class extends Repository {
1857
1822
  }
1858
1823
  };
1859
1824
 
1825
+ // data/WebSocket.ts
1826
+ import {
1827
+ DeleteCommand,
1828
+ DynamoDBDocumentClient,
1829
+ PutCommand,
1830
+ QueryCommand
1831
+ } from "@aws-sdk/lib-dynamodb";
1832
+ var WebSocketRepository = class extends Repository {
1833
+ constructor(tableName, dynamodbClient) {
1834
+ super();
1835
+ this.tableName = tableName;
1836
+ this.dynamodbClient = dynamodbClient;
1837
+ this.docClient = DynamoDBDocumentClient.from(dynamodbClient);
1838
+ }
1839
+ createConnection(connectionId, metadata, expiresAt) {
1840
+ return __async(this, null, function* () {
1841
+ yield this.docClient.send(
1842
+ new PutCommand({
1843
+ TableName: this.tableName,
1844
+ Item: __spreadProps(__spreadValues({
1845
+ PK: `CONN#${connectionId}`,
1846
+ SK: "#METADATA#",
1847
+ connectionId
1848
+ }, metadata), {
1849
+ expiresAt
1850
+ })
1851
+ })
1852
+ );
1853
+ });
1854
+ }
1855
+ getConnection(connectionId) {
1856
+ return __async(this, null, function* () {
1857
+ var _a;
1858
+ const result = yield this.docClient.send(
1859
+ new QueryCommand({
1860
+ TableName: this.tableName,
1861
+ KeyConditionExpression: "PK = :pk",
1862
+ ExpressionAttributeValues: {
1863
+ ":pk": `CONN#${connectionId}`
1864
+ }
1865
+ })
1866
+ );
1867
+ return (_a = result.Items) == null ? void 0 : _a[0];
1868
+ });
1869
+ }
1870
+ deleteConnection(connectionId) {
1871
+ return __async(this, null, function* () {
1872
+ yield this.docClient.send(
1873
+ new DeleteCommand({
1874
+ TableName: this.tableName,
1875
+ Key: {
1876
+ PK: `CONN#${connectionId}`,
1877
+ SK: "#METADATA#"
1878
+ }
1879
+ })
1880
+ );
1881
+ });
1882
+ }
1883
+ createSubscription(subKey, connectionId, data) {
1884
+ return __async(this, null, function* () {
1885
+ yield this.docClient.send(
1886
+ new PutCommand({
1887
+ TableName: this.tableName,
1888
+ Item: __spreadValues({
1889
+ PK: subKey,
1890
+ SK: `CONN#${connectionId}`,
1891
+ R1PK: `CONN#${connectionId}`,
1892
+ R1SK: subKey,
1893
+ connectionId
1894
+ }, data)
1895
+ })
1896
+ );
1897
+ });
1898
+ }
1899
+ deleteSubscription(subKey, connectionId) {
1900
+ return __async(this, null, function* () {
1901
+ yield this.docClient.send(
1902
+ new DeleteCommand({
1903
+ TableName: this.tableName,
1904
+ Key: {
1905
+ PK: subKey,
1906
+ SK: `CONN#${connectionId}`
1907
+ }
1908
+ })
1909
+ );
1910
+ });
1911
+ }
1912
+ querySubscriptionsByKey(subKey) {
1913
+ return __async(this, null, function* () {
1914
+ const result = yield this.docClient.send(
1915
+ new QueryCommand({
1916
+ TableName: this.tableName,
1917
+ KeyConditionExpression: "PK = :pk",
1918
+ ExpressionAttributeValues: {
1919
+ ":pk": subKey
1920
+ },
1921
+ ConsistentRead: true
1922
+ })
1923
+ );
1924
+ return result.Items || [];
1925
+ });
1926
+ }
1927
+ querySubscriptionsByConnectionId(connectionId) {
1928
+ return __async(this, null, function* () {
1929
+ const { ENTITY_REPLICATION_INDEX: ENTITY_REPLICATION_INDEX2 } = yield import("./service.config-ZJEZ6EKA.js");
1930
+ const result = yield this.docClient.send(
1931
+ new QueryCommand({
1932
+ TableName: this.tableName,
1933
+ IndexName: ENTITY_REPLICATION_INDEX2,
1934
+ KeyConditionExpression: "R1PK = :r1pk",
1935
+ ExpressionAttributeValues: {
1936
+ ":r1pk": `CONN#${connectionId}`
1937
+ }
1938
+ })
1939
+ );
1940
+ return result.Items || [];
1941
+ });
1942
+ }
1943
+ createTicket(ticket, entityType, entityId, feedTypes, expiresAt) {
1944
+ return __async(this, null, function* () {
1945
+ yield this.docClient.send(
1946
+ new PutCommand({
1947
+ TableName: this.tableName,
1948
+ Item: {
1949
+ PK: `TICKET#${ticket}`,
1950
+ SK: "#METADATA#",
1951
+ entityType,
1952
+ entityId,
1953
+ feedTypes,
1954
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1955
+ expiresAt
1956
+ }
1957
+ })
1958
+ );
1959
+ });
1960
+ }
1961
+ consumeTicket(ticket) {
1962
+ return __async(this, null, function* () {
1963
+ try {
1964
+ const result = yield this.docClient.send(
1965
+ new DeleteCommand({
1966
+ TableName: this.tableName,
1967
+ Key: {
1968
+ PK: `TICKET#${ticket}`,
1969
+ SK: "#METADATA#"
1970
+ },
1971
+ ConditionExpression: "attribute_exists(PK)",
1972
+ ReturnValues: "ALL_OLD"
1973
+ })
1974
+ );
1975
+ const item = result.Attributes;
1976
+ if (!item) return null;
1977
+ const expiresAt = item.expiresAt;
1978
+ if (expiresAt && expiresAt < Math.floor(Date.now() / 1e3)) {
1979
+ return null;
1980
+ }
1981
+ return {
1982
+ entityType: item.entityType,
1983
+ entityId: item.entityId,
1984
+ feedTypes: item.feedTypes || []
1985
+ };
1986
+ } catch (error) {
1987
+ if (error instanceof Error && error.name === "ConditionalCheckFailedException") {
1988
+ return null;
1989
+ }
1990
+ throw error;
1991
+ }
1992
+ });
1993
+ }
1994
+ queryMutualConnections(byEntityType, byEntityId) {
1995
+ return __async(this, null, function* () {
1996
+ const result = yield this.docClient.send(
1997
+ new QueryCommand({
1998
+ TableName: this.tableName,
1999
+ KeyConditionExpression: "PK = :pk",
2000
+ ExpressionAttributeValues: {
2001
+ ":pk": `${byEntityType}#${byEntityId}`
2002
+ },
2003
+ ProjectionExpression: "SK",
2004
+ ConsistentRead: true
2005
+ })
2006
+ );
2007
+ const connections = [];
2008
+ for (const item of result.Items || []) {
2009
+ const sk = item.SK;
2010
+ if (!sk || sk === "#METADATA#" || sk.startsWith("#")) continue;
2011
+ const parts = sk.split("#");
2012
+ if (parts.length >= 2) {
2013
+ connections.push({ entityType: parts[0], entityId: parts[1] });
2014
+ }
2015
+ }
2016
+ return connections;
2017
+ });
2018
+ }
2019
+ queryFeedSubscriptions(entityType, entityId) {
2020
+ return __async(this, null, function* () {
2021
+ const result = yield this.docClient.send(
2022
+ new QueryCommand({
2023
+ TableName: this.tableName,
2024
+ KeyConditionExpression: "PK = :pk",
2025
+ ExpressionAttributeValues: {
2026
+ ":pk": `SUB#FEED#${entityType}#${entityId}`
2027
+ },
2028
+ ConsistentRead: true
2029
+ })
2030
+ );
2031
+ return result.Items || [];
2032
+ });
2033
+ }
2034
+ };
2035
+
1860
2036
  // handles/app.ts
1861
2037
  import { Hono as Hono2 } from "hono";
1862
2038
  import { handle } from "hono/aws-lambda";
@@ -1896,14 +2072,6 @@ import {
1896
2072
  PutEventsCommand
1897
2073
  } from "@aws-sdk/client-eventbridge";
1898
2074
 
1899
- // constants/table.ts
1900
- var ENTITY_REPLICATION_INDEX = "ENTITY_REPLICATION_INDEX";
1901
- var MUTUAL_REPLICATION_INDEX = "MUTUAL_REPLICATION_INDEX";
1902
-
1903
- // configs/service.config.ts
1904
- var CORE_TABLE = process.env.CORE_TABLE || "";
1905
- var CORE_EVENT_BUS = process.env.CORE_EVENT_BUS || "";
1906
-
1907
2075
  // types/event.ts
1908
2076
  var SOURCE = {
1909
2077
  CORE: "core-service",
@@ -2855,6 +3023,458 @@ var handler5 = (container) => (ev) => __async(null, null, function* () {
2855
3023
  return { batchItemFailures };
2856
3024
  });
2857
3025
 
3026
+ // processors/websocket-processor.ts
3027
+ import {
3028
+ ApiGatewayManagementApiClient,
3029
+ PostToConnectionCommand
3030
+ } from "@aws-sdk/client-apigatewaymanagementapi";
3031
+ import { unmarshall as unmarshall4 } from "@aws-sdk/util-dynamodb";
3032
+ import { ulid as ulid3 } from "ulid";
3033
+ var SUB_ENTITY_TYPE = "SUB#ENTITY#";
3034
+ var SUB_MUTUAL_TYPE = "SUB#MUTUAL#";
3035
+ var SUB_EPHEMERAL = "SUB#EPHEMERAL#";
3036
+ var SUB_FEED = "SUB#FEED#";
3037
+ var getWsEndpoint = () => process.env.WEBSOCKET_MANAGEMENT_ENDPOINT || "";
3038
+ var connect = (container) => (event) => __async(null, null, function* () {
3039
+ var _a, _b, _c, _d;
3040
+ const connectionId = event.requestContext.connectionId;
3041
+ if (!connectionId) {
3042
+ return { statusCode: 400, body: "Missing connection ID" };
3043
+ }
3044
+ const expiresAt = Math.floor(Date.now() / 1e3) + 2 * 60 * 60;
3045
+ const ticket = (_a = event.queryStringParameters) == null ? void 0 : _a.ticket;
3046
+ const token = ((_b = event.queryStringParameters) == null ? void 0 : _b.token) || ((_c = event.headers) == null ? void 0 : _c.authorization) || ((_d = event.headers) == null ? void 0 : _d.Authorization);
3047
+ if (!ticket && !token) {
3048
+ return { statusCode: 401, body: "Unauthorized" };
3049
+ }
3050
+ const wsRepo = container.websocketRepository;
3051
+ try {
3052
+ let entityType;
3053
+ let entityId;
3054
+ let feedTypes;
3055
+ if (ticket) {
3056
+ const ticketData = yield wsRepo.consumeTicket(ticket);
3057
+ if (!ticketData) {
3058
+ return { statusCode: 401, body: "Invalid or expired ticket" };
3059
+ }
3060
+ entityType = ticketData.entityType;
3061
+ entityId = ticketData.entityId;
3062
+ feedTypes = ticketData.feedTypes;
3063
+ } else {
3064
+ entityId = token;
3065
+ }
3066
+ yield wsRepo.createConnection(
3067
+ connectionId,
3068
+ __spreadProps(__spreadValues(__spreadValues({}, entityType && { entityType }), entityId && { entityId }), {
3069
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString()
3070
+ }),
3071
+ expiresAt
3072
+ );
3073
+ if (entityType && entityId && feedTypes) {
3074
+ yield wsRepo.createSubscription(
3075
+ `${SUB_FEED}${entityType}#${entityId}`,
3076
+ connectionId,
3077
+ {
3078
+ subscriptionType: "feed",
3079
+ entityType,
3080
+ entityId,
3081
+ feedTypes,
3082
+ subscribedAt: (/* @__PURE__ */ new Date()).toISOString(),
3083
+ expiresAt
3084
+ }
3085
+ );
3086
+ }
3087
+ return { statusCode: 200, body: "Connected" };
3088
+ } catch (error) {
3089
+ console.error("Error in $connect:", error);
3090
+ return { statusCode: 500, body: "Failed to connect" };
3091
+ }
3092
+ });
3093
+ var disconnect = (container) => (event) => __async(null, null, function* () {
3094
+ const connectionId = event.requestContext.connectionId;
3095
+ if (!connectionId) {
3096
+ return { statusCode: 400, body: "Missing connection ID" };
3097
+ }
3098
+ const wsRepo = container.websocketRepository;
3099
+ try {
3100
+ const subscriptions = yield wsRepo.querySubscriptionsByConnectionId(connectionId);
3101
+ const deletePromises = [];
3102
+ for (const item of subscriptions) {
3103
+ if (item.PK && item.SK) {
3104
+ deletePromises.push(
3105
+ wsRepo.deleteSubscription(item.PK, item.connectionId).catch(
3106
+ (e) => console.warn("Failed to delete subscription on disconnect:", e)
3107
+ )
3108
+ );
3109
+ }
3110
+ }
3111
+ deletePromises.push(wsRepo.deleteConnection(connectionId));
3112
+ yield Promise.all(deletePromises);
3113
+ return { statusCode: 200, body: "Disconnected" };
3114
+ } catch (error) {
3115
+ console.error("Error cleaning up connection:", error);
3116
+ return { statusCode: 500, body: "Failed to disconnect" };
3117
+ }
3118
+ });
3119
+ var $default = (container) => (event) => __async(null, null, function* () {
3120
+ const connectionId = event.requestContext.connectionId;
3121
+ if (!connectionId || !event.body) {
3122
+ return { statusCode: 400, body: "Invalid message" };
3123
+ }
3124
+ let message;
3125
+ try {
3126
+ message = JSON.parse(event.body);
3127
+ } catch (e) {
3128
+ return { statusCode: 400, body: "Invalid JSON" };
3129
+ }
3130
+ const wsRepo = container.websocketRepository;
3131
+ const wsEndpoint = getWsEndpoint();
3132
+ const managementApi = new ApiGatewayManagementApiClient({
3133
+ endpoint: wsEndpoint
3134
+ });
3135
+ try {
3136
+ switch (message.action) {
3137
+ case "subscribe": {
3138
+ const {
3139
+ entityType,
3140
+ byEntityType,
3141
+ byEntityId,
3142
+ mutualEntityType,
3143
+ channel
3144
+ } = message.payload;
3145
+ if (entityType && !byEntityType) {
3146
+ yield wsRepo.createSubscription(
3147
+ `${SUB_ENTITY_TYPE}${entityType}`,
3148
+ connectionId,
3149
+ {
3150
+ subscriptionType: "entity-type",
3151
+ entityType,
3152
+ subscribedAt: (/* @__PURE__ */ new Date()).toISOString()
3153
+ }
3154
+ );
3155
+ } else if (byEntityType && byEntityId && mutualEntityType) {
3156
+ yield wsRepo.createSubscription(
3157
+ `${SUB_MUTUAL_TYPE}${byEntityType}#${byEntityId}#${mutualEntityType}`,
3158
+ connectionId,
3159
+ {
3160
+ subscriptionType: "mutual-type",
3161
+ byEntityType,
3162
+ byEntityId,
3163
+ entityType: mutualEntityType,
3164
+ subscribedAt: (/* @__PURE__ */ new Date()).toISOString()
3165
+ }
3166
+ );
3167
+ } else if (channel) {
3168
+ yield wsRepo.createSubscription(
3169
+ `${SUB_EPHEMERAL}${channel}`,
3170
+ connectionId,
3171
+ {
3172
+ subscriptionType: "ephemeral",
3173
+ channel,
3174
+ subscribedAt: (/* @__PURE__ */ new Date()).toISOString()
3175
+ }
3176
+ );
3177
+ } else {
3178
+ return { statusCode: 400, body: "Invalid subscription parameters" };
3179
+ }
3180
+ const ackMessage = {
3181
+ type: "ack",
3182
+ id: message.id,
3183
+ payload: { action: "subscribe", success: true }
3184
+ };
3185
+ yield managementApi.send(
3186
+ new PostToConnectionCommand({
3187
+ ConnectionId: connectionId,
3188
+ Data: JSON.stringify(ackMessage)
3189
+ })
3190
+ );
3191
+ return { statusCode: 200, body: "Subscribed" };
3192
+ }
3193
+ case "unsubscribe": {
3194
+ const {
3195
+ entityType,
3196
+ byEntityType,
3197
+ byEntityId,
3198
+ mutualEntityType,
3199
+ channel
3200
+ } = message.payload;
3201
+ if (entityType && !byEntityType) {
3202
+ yield wsRepo.deleteSubscription(
3203
+ `${SUB_ENTITY_TYPE}${entityType}`,
3204
+ connectionId
3205
+ );
3206
+ } else if (byEntityType && byEntityId && mutualEntityType) {
3207
+ yield wsRepo.deleteSubscription(
3208
+ `${SUB_MUTUAL_TYPE}${byEntityType}#${byEntityId}#${mutualEntityType}`,
3209
+ connectionId
3210
+ );
3211
+ } else if (channel) {
3212
+ yield wsRepo.deleteSubscription(
3213
+ `${SUB_EPHEMERAL}${channel}`,
3214
+ connectionId
3215
+ );
3216
+ }
3217
+ const ackMessage = {
3218
+ type: "ack",
3219
+ id: message.id,
3220
+ payload: { action: "unsubscribe", success: true }
3221
+ };
3222
+ yield managementApi.send(
3223
+ new PostToConnectionCommand({
3224
+ ConnectionId: connectionId,
3225
+ Data: JSON.stringify(ackMessage)
3226
+ })
3227
+ );
3228
+ return { statusCode: 200, body: "Unsubscribed" };
3229
+ }
3230
+ case "ping": {
3231
+ const pongMessage = {
3232
+ type: "pong",
3233
+ id: message.id,
3234
+ payload: { timestamp: Date.now() }
3235
+ };
3236
+ yield managementApi.send(
3237
+ new PostToConnectionCommand({
3238
+ ConnectionId: connectionId,
3239
+ Data: JSON.stringify(pongMessage)
3240
+ })
3241
+ );
3242
+ return { statusCode: 200, body: "Pong" };
3243
+ }
3244
+ case "ephemeral": {
3245
+ const { channel, data } = message.payload;
3246
+ if (!channel) {
3247
+ return { statusCode: 400, body: "Missing channel" };
3248
+ }
3249
+ const conn = yield wsRepo.getConnection(connectionId);
3250
+ const senderId = conn == null ? void 0 : conn.entityId;
3251
+ const subKey = `${SUB_EPHEMERAL}${channel}`;
3252
+ const ephemeralMessage = {
3253
+ type: "ephemeral",
3254
+ id: ulid3(),
3255
+ payload: { channel, data, senderId }
3256
+ };
3257
+ yield broadcastToSubscribers(
3258
+ managementApi,
3259
+ wsRepo,
3260
+ subKey,
3261
+ ephemeralMessage,
3262
+ connectionId
3263
+ // Exclude sender
3264
+ );
3265
+ return { statusCode: 200, body: "Broadcasted" };
3266
+ }
3267
+ default:
3268
+ return { statusCode: 400, body: "Unknown action" };
3269
+ }
3270
+ } catch (error) {
3271
+ console.error("Error handling message:", error);
3272
+ try {
3273
+ const errorMessage = {
3274
+ type: "error",
3275
+ id: message.id,
3276
+ payload: { message: "Internal server error" }
3277
+ };
3278
+ yield managementApi.send(
3279
+ new PostToConnectionCommand({
3280
+ ConnectionId: connectionId,
3281
+ Data: JSON.stringify(errorMessage)
3282
+ })
3283
+ );
3284
+ } catch (e) {
3285
+ }
3286
+ return { statusCode: 500, body: "Internal server error" };
3287
+ }
3288
+ });
3289
+ var broadcast = (container) => (event) => __async(null, null, function* () {
3290
+ var _a, _b, _c, _d;
3291
+ const wsRepo = container.websocketRepository;
3292
+ const wsEndpoint = getWsEndpoint();
3293
+ const managementApi = new ApiGatewayManagementApiClient({
3294
+ endpoint: wsEndpoint
3295
+ });
3296
+ for (const record of event.Records) {
3297
+ const isInsert = record.eventName === "INSERT";
3298
+ const isModify = record.eventName === "MODIFY";
3299
+ const isRemove = record.eventName === "REMOVE";
3300
+ if (!isInsert && !isModify && !isRemove) continue;
3301
+ const newImage = (_a = record.dynamodb) == null ? void 0 : _a.NewImage;
3302
+ const oldImage = (_b = record.dynamodb) == null ? void 0 : _b.OldImage;
3303
+ const image = newImage || oldImage;
3304
+ if (!image) continue;
3305
+ const pk = ((_c = image.PK) == null ? void 0 : _c.S) || "";
3306
+ const sk = ((_d = image.SK) == null ? void 0 : _d.S) || "";
3307
+ const pkParts = pk.split("#");
3308
+ if (pkParts.length < 2) continue;
3309
+ const firstPart = pkParts[0];
3310
+ if (firstPart === firstPart.toUpperCase() || firstPart.includes(":")) {
3311
+ continue;
3312
+ }
3313
+ const entityType = pkParts[0];
3314
+ const entityId = pkParts[1];
3315
+ const isMutual = !sk.startsWith("#METADATA#") && sk.includes("#");
3316
+ try {
3317
+ if (isMutual) {
3318
+ const skParts = sk.split("#");
3319
+ const mutualEntityType = skParts[0];
3320
+ const byEntityId = entityId;
3321
+ const subKey = `${SUB_MUTUAL_TYPE}${entityType}#${byEntityId}#${mutualEntityType}`;
3322
+ const subscribers = yield wsRepo.querySubscriptionsByKey(subKey);
3323
+ if (subscribers.length) {
3324
+ let eventType;
3325
+ if (isInsert) eventType = "mutual.created";
3326
+ else if (isModify) eventType = "mutual.updated";
3327
+ else eventType = "mutual.deleted";
3328
+ const message = {
3329
+ type: eventType,
3330
+ id: ulid3(),
3331
+ payload: {
3332
+ byEntityType: entityType,
3333
+ byEntityId,
3334
+ mutualEntityType,
3335
+ entityId: skParts[1],
3336
+ data: isRemove ? void 0 : unmarshall4(image)
3337
+ }
3338
+ };
3339
+ yield broadcastToSubscribers(
3340
+ managementApi,
3341
+ wsRepo,
3342
+ subKey,
3343
+ message
3344
+ );
3345
+ }
3346
+ } else {
3347
+ const subKey = `${SUB_ENTITY_TYPE}${entityType}`;
3348
+ const subscribers = yield wsRepo.querySubscriptionsByKey(subKey);
3349
+ if (subscribers.length) {
3350
+ let eventType;
3351
+ if (isInsert) eventType = "entity.created";
3352
+ else if (isModify) eventType = "entity.updated";
3353
+ else eventType = "entity.deleted";
3354
+ const message = {
3355
+ type: eventType,
3356
+ id: ulid3(),
3357
+ payload: {
3358
+ entityType,
3359
+ entityId,
3360
+ data: isRemove ? void 0 : unmarshall4(image)
3361
+ }
3362
+ };
3363
+ yield broadcastToSubscribers(
3364
+ managementApi,
3365
+ wsRepo,
3366
+ subKey,
3367
+ message
3368
+ );
3369
+ }
3370
+ }
3371
+ yield broadcastToFeedSubscribers(
3372
+ managementApi,
3373
+ wsRepo,
3374
+ entityType,
3375
+ entityId,
3376
+ isMutual ? sk.split("#")[0] : entityType,
3377
+ // the changed entity type
3378
+ isMutual ? {
3379
+ type: isInsert ? "mutual.created" : isModify ? "mutual.updated" : "mutual.deleted",
3380
+ id: ulid3(),
3381
+ payload: {
3382
+ byEntityType: entityType,
3383
+ byEntityId: entityId,
3384
+ mutualEntityType: sk.split("#")[0],
3385
+ entityId: sk.split("#")[1],
3386
+ data: isRemove ? void 0 : unmarshall4(image)
3387
+ }
3388
+ } : {
3389
+ type: isInsert ? "entity.created" : isModify ? "entity.updated" : "entity.deleted",
3390
+ id: ulid3(),
3391
+ payload: {
3392
+ entityType,
3393
+ entityId,
3394
+ data: isRemove ? void 0 : unmarshall4(image)
3395
+ }
3396
+ }
3397
+ );
3398
+ } catch (error) {
3399
+ console.error("Error broadcasting:", error);
3400
+ }
3401
+ }
3402
+ });
3403
+ function broadcastToSubscribers(managementApi, wsRepo, subKey, message, excludeConnectionId) {
3404
+ return __async(this, null, function* () {
3405
+ const subscribers = yield wsRepo.querySubscriptionsByKey(subKey);
3406
+ if (!subscribers.length) return;
3407
+ const messageData = JSON.stringify(message);
3408
+ const sends = subscribers.filter((subscriber) => {
3409
+ const id = subscriber.connectionId;
3410
+ return !excludeConnectionId || id !== excludeConnectionId;
3411
+ }).map((subscriber) => __async(null, null, function* () {
3412
+ var _a;
3413
+ try {
3414
+ yield managementApi.send(
3415
+ new PostToConnectionCommand({
3416
+ ConnectionId: subscriber.connectionId,
3417
+ Data: messageData
3418
+ })
3419
+ );
3420
+ } catch (error) {
3421
+ const isGone = (error == null ? void 0 : error.name) === "GoneException" || ((_a = error == null ? void 0 : error.$metadata) == null ? void 0 : _a.httpStatusCode) === 410;
3422
+ if (isGone) {
3423
+ yield wsRepo.deleteSubscription(subKey, subscriber.connectionId).catch(
3424
+ (e) => console.warn("Failed to clean up stale subscription:", e)
3425
+ );
3426
+ }
3427
+ }
3428
+ }));
3429
+ yield Promise.allSettled(sends);
3430
+ });
3431
+ }
3432
+ function broadcastToFeedSubscribers(managementApi, wsRepo, byEntityType, byEntityId, changedEntityType, message) {
3433
+ return __async(this, null, function* () {
3434
+ var _a;
3435
+ const connections = yield wsRepo.queryMutualConnections(
3436
+ byEntityType,
3437
+ byEntityId
3438
+ );
3439
+ if (!connections.length) return;
3440
+ const connectedEntities = new Set(
3441
+ connections.map((c) => `${c.entityType}:${c.entityId}`)
3442
+ );
3443
+ connectedEntities.add(`${byEntityType}:${byEntityId}`);
3444
+ const sentConnections = /* @__PURE__ */ new Set();
3445
+ for (const connEntity of connectedEntities) {
3446
+ const [entityType, entityId] = connEntity.split(":");
3447
+ const feedSubs = yield wsRepo.queryFeedSubscriptions(entityType, entityId);
3448
+ if (!feedSubs.length) continue;
3449
+ for (const feedSub of feedSubs) {
3450
+ const feedTypes = feedSub.feedTypes;
3451
+ const connectionId = feedSub.connectionId;
3452
+ if (feedTypes && !feedTypes.includes(changedEntityType)) continue;
3453
+ if (sentConnections.has(connectionId)) continue;
3454
+ sentConnections.add(connectionId);
3455
+ try {
3456
+ yield managementApi.send(
3457
+ new PostToConnectionCommand({
3458
+ ConnectionId: connectionId,
3459
+ Data: JSON.stringify(message)
3460
+ })
3461
+ );
3462
+ } catch (error) {
3463
+ const isGone = (error == null ? void 0 : error.name) === "GoneException" || ((_a = error == null ? void 0 : error.$metadata) == null ? void 0 : _a.httpStatusCode) === 410;
3464
+ if (isGone) {
3465
+ yield wsRepo.deleteSubscription(
3466
+ `SUB#FEED#${entityType}#${entityId}`,
3467
+ connectionId
3468
+ ).catch(
3469
+ (e) => console.warn("Failed to clean up stale feed subscription:", e)
3470
+ );
3471
+ }
3472
+ }
3473
+ }
3474
+ }
3475
+ });
3476
+ }
3477
+
2858
3478
  // services/DependencyContainer.ts
2859
3479
  import { DynamoDB } from "@aws-sdk/client-dynamodb";
2860
3480
 
@@ -7091,7 +7711,16 @@ var UpdateEntityController = class {
7091
7711
  const accountId = c.req.header("account-id");
7092
7712
  const { entityType, entityId } = c.req.param();
7093
7713
  const body = yield c.req.json();
7094
- const _a = body, { $where: where } = _a, entityPayload = __objRest(_a, ["$where"]);
7714
+ const _a = body, { $condition: condition, $where: where } = _a, entityPayload = __objRest(_a, ["$condition", "$where"]);
7715
+ if (condition !== void 0) {
7716
+ if (typeof condition !== "string" || condition.trim().length === 0) {
7717
+ c.status(httpStatus8.BAD_REQUEST);
7718
+ return c.json({
7719
+ code: "API_VALIDATION_ERROR",
7720
+ message: "$condition must be a non-empty string"
7721
+ });
7722
+ }
7723
+ }
7095
7724
  const errorContext = {
7096
7725
  accountId,
7097
7726
  "req.params": c.req.param(),
@@ -7103,6 +7732,7 @@ var UpdateEntityController = class {
7103
7732
  entityId,
7104
7733
  entityPayload,
7105
7734
  accountId,
7735
+ condition,
7106
7736
  where
7107
7737
  });
7108
7738
  errorContext.entity = entity;
@@ -7121,11 +7751,15 @@ var UpdateEntityController = class {
7121
7751
  c.status(httpStatus8.NOT_FOUND);
7122
7752
  return c.json(__spreadValues({}, err.toJSON()));
7123
7753
  }
7124
- if (err instanceof StandardError && err.code === StandardErrorCode.UNIQUE_VALUE_EXISTS) {
7754
+ if (err instanceof StandardError && err.code === StandardErrorCode.INVALID_CONDITION) {
7125
7755
  c.status(httpStatus8.BAD_REQUEST);
7126
7756
  return c.json(__spreadValues({}, err.toJSON()));
7127
7757
  }
7128
- if (err instanceof StandardError && err.code === StandardErrorCode.CONDITIONAL_CHECK_FAILED) {
7758
+ if (err instanceof StandardError && err.code === StandardErrorCode.UNIQUE_VALUE_EXISTS) {
7759
+ c.status(httpStatus8.BAD_REQUEST);
7760
+ return c.json(__spreadValues({}, err.toJSON()));
7761
+ }
7762
+ if (err instanceof StandardError && err.code === StandardErrorCode.CONDITIONAL_CHECK_FAILED) {
7129
7763
  c.status(httpStatus8.CONFLICT);
7130
7764
  return c.json(__spreadValues({}, err.toJSON()));
7131
7765
  }
@@ -7147,11 +7781,21 @@ var AdjustEntityController = class {
7147
7781
  constructor(entityService) {
7148
7782
  this.entityService = entityService;
7149
7783
  this.controller = createMiddleware9((c) => __async(this, null, function* () {
7150
- var _a;
7784
+ var _b;
7151
7785
  const accountId = c.req.header("account-id") || "";
7152
7786
  const { entityType, entityId } = c.req.param();
7153
7787
  const body = yield c.req.json();
7154
- for (const [key, value] of Object.entries(body)) {
7788
+ const _a = body, { $condition: condition } = _a, adjustments = __objRest(_a, ["$condition"]);
7789
+ if (condition !== void 0) {
7790
+ if (typeof condition !== "string" || condition.trim().length === 0) {
7791
+ c.status(httpStatus9.BAD_REQUEST);
7792
+ return c.json({
7793
+ code: "API_VALIDATION_ERROR",
7794
+ message: "$condition must be a non-empty string"
7795
+ });
7796
+ }
7797
+ }
7798
+ for (const [key, value] of Object.entries(adjustments)) {
7155
7799
  if (typeof value !== "number") {
7156
7800
  c.status(httpStatus9.BAD_REQUEST);
7157
7801
  return c.json({
@@ -7164,8 +7808,9 @@ var AdjustEntityController = class {
7164
7808
  const entity = yield this.entityService.adjustEntity({
7165
7809
  entityType,
7166
7810
  entityId,
7167
- adjustments: body,
7168
- accountId
7811
+ adjustments,
7812
+ accountId,
7813
+ condition
7169
7814
  });
7170
7815
  c.status(httpStatus9.OK);
7171
7816
  return c.json(entity.toJSON());
@@ -7174,7 +7819,11 @@ var AdjustEntityController = class {
7174
7819
  c.status(httpStatus9.NOT_FOUND);
7175
7820
  return c.json(__spreadValues({}, err.toJSON()));
7176
7821
  }
7177
- if ((err == null ? void 0 : err.name) === "ConditionalCheckFailedException" || ((_a = err == null ? void 0 : err.__type) == null ? void 0 : _a.includes("ConditionalCheckFailed"))) {
7822
+ if (err instanceof StandardError && err.code === StandardErrorCode.INVALID_CONDITION) {
7823
+ c.status(httpStatus9.BAD_REQUEST);
7824
+ return c.json(__spreadValues({}, err.toJSON()));
7825
+ }
7826
+ if ((err == null ? void 0 : err.name) === "ConditionalCheckFailedException" || ((_b = err == null ? void 0 : err.__type) == null ? void 0 : _b.includes("ConditionalCheckFailed"))) {
7178
7827
  c.status(httpStatus9.CONFLICT);
7179
7828
  return c.json({
7180
7829
  code: "ADJUSTMENT_CONSTRAINT_VIOLATED",
@@ -7479,6 +8128,9 @@ var UpdateMutualController = class {
7479
8128
  }
7480
8129
  };
7481
8130
 
8131
+ // services/entity.service.ts
8132
+ import { marshall as marshall6 } from "@aws-sdk/util-dynamodb";
8133
+
7482
8134
  // data/utils/build-condition-expression.ts
7483
8135
  import { marshall as marshall5 } from "@aws-sdk/util-dynamodb";
7484
8136
  function buildConditionExpression(where) {
@@ -7540,6 +8192,55 @@ function buildConditionExpression(where) {
7540
8192
  };
7541
8193
  }
7542
8194
 
8195
+ // services/resolve-condition.ts
8196
+ function resolveAdjustmentCondition(_0) {
8197
+ return __async(this, arguments, function* ({
8198
+ conditionName,
8199
+ conditions,
8200
+ adjustments,
8201
+ getEntityData
8202
+ }) {
8203
+ if (!Object.hasOwn(conditions, conditionName)) {
8204
+ throw new StandardError(
8205
+ StandardErrorCode.INVALID_CONDITION,
8206
+ `Unknown adjustment condition: '${conditionName}'`
8207
+ );
8208
+ }
8209
+ const condition = conditions[conditionName];
8210
+ let resolved;
8211
+ if (typeof condition === "function") {
8212
+ const data = yield getEntityData();
8213
+ resolved = condition(data, adjustments);
8214
+ } else {
8215
+ resolved = condition;
8216
+ }
8217
+ return buildConditionExpression(resolved);
8218
+ });
8219
+ }
8220
+ function resolveUpdateCondition(_0) {
8221
+ return __async(this, arguments, function* ({
8222
+ conditionName,
8223
+ conditions,
8224
+ getEntityData
8225
+ }) {
8226
+ if (!Object.hasOwn(conditions, conditionName)) {
8227
+ throw new StandardError(
8228
+ StandardErrorCode.INVALID_CONDITION,
8229
+ `Unknown update condition: '${conditionName}'`
8230
+ );
8231
+ }
8232
+ const condition = conditions[conditionName];
8233
+ let resolved;
8234
+ if (typeof condition === "function") {
8235
+ const data = yield getEntityData();
8236
+ resolved = condition(data);
8237
+ } else {
8238
+ resolved = condition;
8239
+ }
8240
+ return buildConditionExpression(resolved);
8241
+ });
8242
+ }
8243
+
7543
8244
  // services/entity.service.ts
7544
8245
  var EntityService = class {
7545
8246
  constructor(EntityConfig, EmailAuthEnabledEntities, entityRepository, publishEvent2, entityServiceLifeCycle) {
@@ -7595,34 +8296,59 @@ var EntityService = class {
7595
8296
  entityType,
7596
8297
  entityId,
7597
8298
  adjustments,
7598
- accountId
8299
+ accountId,
8300
+ condition
7599
8301
  }) {
7600
- var _a, _b, _c, _d;
7601
- const rawConstraints = (_a = this.EntityConfig[entityType]) == null ? void 0 : _a.adjustmentConstraints;
7602
- let resolvedConstraints = rawConstraints;
7603
- if (rawConstraints) {
8302
+ var _a, _b, _c;
8303
+ const entityConfig = this.EntityConfig[entityType];
8304
+ const adjustmentConditions = entityConfig == null ? void 0 : entityConfig.adjustmentConditions;
8305
+ const rawConstraints = entityConfig == null ? void 0 : entityConfig.adjustmentConstraints;
8306
+ let opts;
8307
+ if (adjustmentConditions) {
8308
+ if (!condition) {
8309
+ throw new StandardError(
8310
+ StandardErrorCode.INVALID_CONDITION,
8311
+ "Entity has adjustmentConditions defined; $condition is required for adjustEntity"
8312
+ );
8313
+ }
8314
+ opts = yield resolveAdjustmentCondition({
8315
+ conditionName: condition,
8316
+ conditions: adjustmentConditions,
8317
+ adjustments,
8318
+ getEntityData: () => __async(this, null, function* () {
8319
+ var _a2;
8320
+ const entity2 = yield this.entityRepository.getEntity(entityType, entityId);
8321
+ return (_a2 = entity2 == null ? void 0 : entity2.data) != null ? _a2 : {};
8322
+ })
8323
+ });
8324
+ } else if (rawConstraints) {
8325
+ console.warn(
8326
+ "[monorise] adjustmentConstraints is deprecated. Use adjustmentConditions instead."
8327
+ );
8328
+ let resolvedConstraints = rawConstraints;
7604
8329
  const hasDynamicFields = Object.values(rawConstraints).some(
7605
8330
  (c) => c.minField || c.maxField
7606
8331
  );
7607
8332
  if (hasDynamicFields) {
7608
8333
  const currentEntity = yield this.entityRepository.getEntity(entityType, entityId);
7609
- const data = (_b = currentEntity == null ? void 0 : currentEntity.data) != null ? _b : {};
8334
+ const data = (_a = currentEntity == null ? void 0 : currentEntity.data) != null ? _a : {};
7610
8335
  resolvedConstraints = {};
7611
8336
  for (const [field, constraint] of Object.entries(rawConstraints)) {
7612
8337
  const resolved = {};
7613
8338
  if (constraint.min !== void 0) resolved.min = constraint.min;
7614
8339
  if (constraint.max !== void 0) resolved.max = constraint.max;
7615
- if (constraint.minField) resolved.min = (_c = data[constraint.minField]) != null ? _c : 0;
7616
- if (constraint.maxField) resolved.max = (_d = data[constraint.maxField]) != null ? _d : Number.MAX_SAFE_INTEGER;
8340
+ if (constraint.minField) resolved.min = (_b = data[constraint.minField]) != null ? _b : 0;
8341
+ if (constraint.maxField) resolved.max = (_c = data[constraint.maxField]) != null ? _c : Number.MAX_SAFE_INTEGER;
7617
8342
  resolvedConstraints[field] = resolved;
7618
8343
  }
7619
8344
  }
8345
+ opts = this.buildLegacyAdjustCondition(adjustments, resolvedConstraints);
7620
8346
  }
7621
8347
  const entity = yield this.entityRepository.adjustEntity(
7622
8348
  entityType,
7623
8349
  entityId,
7624
8350
  adjustments,
7625
- resolvedConstraints
8351
+ opts
7626
8352
  );
7627
8353
  yield this.publishEvent({
7628
8354
  event: EVENT.CORE.ENTITY_UPDATED,
@@ -7641,9 +8367,10 @@ var EntityService = class {
7641
8367
  entityId,
7642
8368
  entityPayload,
7643
8369
  accountId,
8370
+ condition,
7644
8371
  where
7645
8372
  }) {
7646
- var _a, _b;
8373
+ var _a, _b, _c;
7647
8374
  const errorContext = {};
7648
8375
  try {
7649
8376
  const entitySchema = this.EntityConfig[entityType].baseSchema;
@@ -7657,7 +8384,30 @@ var EntityService = class {
7657
8384
  const parsedEntityPayload = entitySchema.parse(entityPayload);
7658
8385
  const parsedMutualPayload = mutualSchema == null ? void 0 : mutualSchema.parse(entityPayload);
7659
8386
  errorContext.parsedMutualPayload = parsedMutualPayload;
7660
- const opts = where && Object.keys(where).length > 0 ? buildConditionExpression(where) : void 0;
8387
+ let opts;
8388
+ if (condition) {
8389
+ const updateConditions = (_b = this.EntityConfig[entityType]) == null ? void 0 : _b.updateConditions;
8390
+ if (!updateConditions) {
8391
+ throw new StandardError(
8392
+ StandardErrorCode.INVALID_CONDITION,
8393
+ `Entity '${entityType}' has no updateConditions defined`
8394
+ );
8395
+ }
8396
+ opts = yield resolveUpdateCondition({
8397
+ conditionName: condition,
8398
+ conditions: updateConditions,
8399
+ getEntityData: () => __async(this, null, function* () {
8400
+ var _a2;
8401
+ const entity2 = yield this.entityRepository.getEntity(entityType, entityId);
8402
+ return (_a2 = entity2 == null ? void 0 : entity2.data) != null ? _a2 : {};
8403
+ })
8404
+ });
8405
+ } else if (where && Object.keys(where).length > 0) {
8406
+ console.warn(
8407
+ "[monorise] $where is deprecated. Use named conditions via $condition instead."
8408
+ );
8409
+ opts = buildConditionExpression(where);
8410
+ }
7661
8411
  const entity = yield this.entityRepository.updateEntity(
7662
8412
  entityType,
7663
8413
  entityId,
@@ -7670,7 +8420,7 @@ var EntityService = class {
7670
8420
  const byEntityId = entityId;
7671
8421
  const publishEventPromises = [];
7672
8422
  for (const [fieldKey, config] of Object.entries(
7673
- ((_b = this.EntityConfig[entityType].mutual) == null ? void 0 : _b.mutualFields) || {}
8423
+ ((_c = this.EntityConfig[entityType].mutual) == null ? void 0 : _c.mutualFields) || {}
7674
8424
  )) {
7675
8425
  const toMutualIds = config.toMutualIds;
7676
8426
  const mutualPayload = parsedMutualPayload[fieldKey];
@@ -7726,10 +8476,39 @@ var EntityService = class {
7726
8476
  });
7727
8477
  });
7728
8478
  }
8479
+ /** @deprecated Converts legacy adjustmentConstraints to condition expression opts. */
8480
+ buildLegacyAdjustCondition(adjustments, constraints) {
8481
+ const conditionParts = [];
8482
+ const names = { "#data": "data" };
8483
+ const values = {};
8484
+ for (const [field, constraint] of Object.entries(constraints)) {
8485
+ const delta = adjustments[field];
8486
+ if (delta === void 0) continue;
8487
+ const namePlaceholder = `#where_${field}`;
8488
+ names[namePlaceholder] = field;
8489
+ const fieldRef = `#data.${namePlaceholder}`;
8490
+ if (constraint.min !== void 0 && delta < 0) {
8491
+ const valKey = `:where_${field}_min_threshold`;
8492
+ conditionParts.push(`${fieldRef} >= ${valKey}`);
8493
+ values[valKey] = constraint.min - delta;
8494
+ }
8495
+ if (constraint.max !== void 0 && delta > 0) {
8496
+ const valKey = `:where_${field}_max_threshold`;
8497
+ conditionParts.push(`${fieldRef} <= ${valKey}`);
8498
+ values[valKey] = constraint.max - delta;
8499
+ }
8500
+ }
8501
+ if (conditionParts.length === 0) return void 0;
8502
+ return {
8503
+ ConditionExpression: conditionParts.join(" AND "),
8504
+ ExpressionAttributeNames: names,
8505
+ ExpressionAttributeValues: marshall6(values)
8506
+ };
8507
+ }
7729
8508
  };
7730
8509
 
7731
8510
  // services/mutual.service.ts
7732
- import { ulid as ulid3 } from "ulid";
8511
+ import { ulid as ulid4 } from "ulid";
7733
8512
  var MutualService = class {
7734
8513
  constructor(entityRepository, mutualRepository, publishEvent2, ddbUtils, entityServiceLifeCycle) {
7735
8514
  this.entityRepository = entityRepository;
@@ -7789,7 +8568,7 @@ var MutualService = class {
7789
8568
  entityId,
7790
8569
  entityData,
7791
8570
  parsedMutualPayload,
7792
- mutualId || ulid3(),
8571
+ mutualId || ulid4(),
7793
8572
  currentDatetime,
7794
8573
  currentDatetime,
7795
8574
  currentDatetime
@@ -7967,6 +8746,116 @@ var ListTagsController = class {
7967
8746
  }
7968
8747
  };
7969
8748
 
8749
+ // controllers/transaction/execute-transaction.controller.ts
8750
+ import { createMiddleware as createMiddleware17 } from "hono/factory";
8751
+ import httpStatus15 from "http-status";
8752
+ var ExecuteTransactionController = class {
8753
+ constructor(transactionService) {
8754
+ this.transactionService = transactionService;
8755
+ // biome-ignore lint/suspicious/noExplicitAny: Hono createMiddleware requires consistent return types
8756
+ this.controller = createMiddleware17((c) => __async(this, null, function* () {
8757
+ var _a;
8758
+ const accountId = c.req.header("account-id") || "";
8759
+ const body = yield c.req.json();
8760
+ if (!body.operations || !Array.isArray(body.operations)) {
8761
+ c.status(httpStatus15.BAD_REQUEST);
8762
+ return c.json({
8763
+ code: "API_VALIDATION_ERROR",
8764
+ message: 'Request body must contain an "operations" array'
8765
+ });
8766
+ }
8767
+ try {
8768
+ const result = yield this.transactionService.executeTransaction(
8769
+ body.operations,
8770
+ accountId
8771
+ );
8772
+ c.status(httpStatus15.OK);
8773
+ return c.json(result);
8774
+ } catch (err) {
8775
+ if (((_a = err.constructor) == null ? void 0 : _a.name) === "ZodError") {
8776
+ c.status(httpStatus15.BAD_REQUEST);
8777
+ return c.json({
8778
+ code: "API_VALIDATION_ERROR",
8779
+ message: "Validation failed",
8780
+ details: err.flatten()
8781
+ });
8782
+ }
8783
+ if (err instanceof StandardError) {
8784
+ const code = err.code;
8785
+ if (code === StandardErrorCode.TRANSACTION_EMPTY || code === StandardErrorCode.TRANSACTION_ITEM_LIMIT_EXCEEDED || code === StandardErrorCode.TRANSACTION_UNIQUE_FIELD_UPDATE || code === StandardErrorCode.INVALID_ENTITY_TYPE || code === StandardErrorCode.INVALID_CONDITION || code === StandardErrorCode.INVALID_UNIQUE_VALUE_TYPE) {
8786
+ c.status(httpStatus15.BAD_REQUEST);
8787
+ return c.json(__spreadValues({}, err.toJSON()));
8788
+ }
8789
+ if (code === StandardErrorCode.TRANSACTION_FAILED || code === StandardErrorCode.CONDITIONAL_CHECK_FAILED || code === StandardErrorCode.UNIQUE_VALUE_EXISTS) {
8790
+ c.status(httpStatus15.CONFLICT);
8791
+ return c.json(__spreadValues({}, err.toJSON()));
8792
+ }
8793
+ }
8794
+ throw err;
8795
+ }
8796
+ }));
8797
+ }
8798
+ };
8799
+
8800
+ // controllers/ws/create-ticket.controller.ts
8801
+ import { createMiddleware as createMiddleware18 } from "hono/factory";
8802
+ import { ulid as ulid5 } from "ulid";
8803
+ var TICKET_TTL_SECONDS = 30 * 60;
8804
+ var CreateTicketController = class {
8805
+ constructor(container) {
8806
+ this.container = container;
8807
+ this.controller = createMiddleware18((c) => __async(this, null, function* () {
8808
+ var _a;
8809
+ const { entityType, entityId } = c.req.param();
8810
+ let feedTypes;
8811
+ try {
8812
+ const body = yield c.req.json();
8813
+ feedTypes = body.feedTypes;
8814
+ } catch (e) {
8815
+ }
8816
+ if (!feedTypes || feedTypes.length === 0) {
8817
+ const allConfigs = this.container.config.EntityConfig;
8818
+ const visited = /* @__PURE__ */ new Set();
8819
+ const queue = [entityType];
8820
+ while (queue.length > 0) {
8821
+ const current = queue.shift();
8822
+ if (!current) continue;
8823
+ if (visited.has(current)) continue;
8824
+ visited.add(current);
8825
+ const config = allConfigs[current];
8826
+ if ((_a = config == null ? void 0 : config.mutual) == null ? void 0 : _a.mutualFields) {
8827
+ for (const field of Object.values(
8828
+ config.mutual.mutualFields
8829
+ )) {
8830
+ if (!visited.has(field.entityType)) {
8831
+ queue.push(field.entityType);
8832
+ }
8833
+ }
8834
+ }
8835
+ }
8836
+ visited.delete(entityType);
8837
+ feedTypes = Array.from(visited);
8838
+ }
8839
+ const ticket = ulid5();
8840
+ const now = Math.floor(Date.now() / 1e3);
8841
+ const expiresAt = now + TICKET_TTL_SECONDS;
8842
+ yield this.container.websocketRepository.createTicket(
8843
+ ticket,
8844
+ entityType,
8845
+ entityId,
8846
+ feedTypes,
8847
+ expiresAt
8848
+ );
8849
+ const wsEndpoint = process.env.WEBSOCKET_URL || "";
8850
+ return c.json({
8851
+ ticket,
8852
+ wsUrl: wsEndpoint,
8853
+ expiresIn: TICKET_TTL_SECONDS
8854
+ });
8855
+ }));
8856
+ }
8857
+ };
8858
+
7970
8859
  // services/entity-service-lifecycle.ts
7971
8860
  var EntityServiceLifeCycle = class {
7972
8861
  constructor(EntityConfig, publishEvent2, eventUtils) {
@@ -7999,6 +8888,435 @@ var EntityServiceLifeCycle = class {
7999
8888
  }
8000
8889
  };
8001
8890
 
8891
+ // services/transaction.service.ts
8892
+ import { TransactionCanceledException as TransactionCanceledException5 } from "@aws-sdk/client-dynamodb";
8893
+ import { ulid as ulid6 } from "ulid";
8894
+ var MAX_TRANSACTION_ITEMS = 100;
8895
+ var TransactionService = class {
8896
+ constructor(EntityConfig, EmailAuthEnabledEntities, entityRepository, dynamodbClient, publishEvent2, entityServiceLifeCycle, eventUtils) {
8897
+ this.EntityConfig = EntityConfig;
8898
+ this.EmailAuthEnabledEntities = EmailAuthEnabledEntities;
8899
+ this.entityRepository = entityRepository;
8900
+ this.dynamodbClient = dynamodbClient;
8901
+ this.publishEvent = publishEvent2;
8902
+ this.entityServiceLifeCycle = entityServiceLifeCycle;
8903
+ this.eventUtils = eventUtils;
8904
+ this.executeTransaction = (operations, accountId) => __async(this, null, function* () {
8905
+ var _a;
8906
+ if (!operations || operations.length === 0) {
8907
+ throw new StandardError(
8908
+ StandardErrorCode.TRANSACTION_EMPTY,
8909
+ "Transaction must contain at least one operation"
8910
+ );
8911
+ }
8912
+ const allTransactItems = [];
8913
+ const pendingEvents = [];
8914
+ const resultEntries = [];
8915
+ for (const op of operations) {
8916
+ switch (op.operation) {
8917
+ case "createEntity": {
8918
+ const { items, entity } = yield this.buildCreateItems(op);
8919
+ allTransactItems.push(...items);
8920
+ pendingEvents.push(
8921
+ ...this.collectCreateEvents(
8922
+ entity,
8923
+ op.payload,
8924
+ accountId
8925
+ )
8926
+ );
8927
+ resultEntries.push({
8928
+ operation: "createEntity",
8929
+ entityType: op.entityType,
8930
+ entityId: entity.entityId,
8931
+ data: entity.data
8932
+ });
8933
+ break;
8934
+ }
8935
+ case "updateEntity": {
8936
+ const { item, updatedAt } = yield this.buildUpdateItem(op);
8937
+ allTransactItems.push(item);
8938
+ pendingEvents.push(
8939
+ ...this.collectUpdateEvents(
8940
+ op,
8941
+ updatedAt,
8942
+ accountId
8943
+ )
8944
+ );
8945
+ resultEntries.push({
8946
+ operation: "updateEntity",
8947
+ entityType: op.entityType,
8948
+ entityId: op.entityId
8949
+ });
8950
+ break;
8951
+ }
8952
+ case "adjustEntity": {
8953
+ const { item, updatedAt } = yield this.buildAdjustItem(op);
8954
+ allTransactItems.push(item);
8955
+ pendingEvents.push({
8956
+ event: EVENT.CORE.ENTITY_UPDATED,
8957
+ payload: {
8958
+ entityType: op.entityType,
8959
+ entityId: op.entityId,
8960
+ updatedByAccountId: accountId,
8961
+ publishedAt: updatedAt
8962
+ }
8963
+ });
8964
+ resultEntries.push({
8965
+ operation: "adjustEntity",
8966
+ entityType: op.entityType,
8967
+ entityId: op.entityId
8968
+ });
8969
+ break;
8970
+ }
8971
+ case "deleteEntity": {
8972
+ const item = this.buildDeleteItem(op);
8973
+ allTransactItems.push(item);
8974
+ pendingEvents.push({
8975
+ event: EVENT.CORE.ENTITY_DELETED,
8976
+ payload: {
8977
+ entityType: op.entityType,
8978
+ entityId: op.entityId,
8979
+ deletedByAccountId: accountId
8980
+ }
8981
+ });
8982
+ resultEntries.push({
8983
+ operation: "deleteEntity",
8984
+ entityType: op.entityType,
8985
+ entityId: op.entityId
8986
+ });
8987
+ break;
8988
+ }
8989
+ default:
8990
+ throw new StandardError(
8991
+ StandardErrorCode.INVALID_ENTITY_TYPE,
8992
+ `Unknown operation: '${op.operation}'`
8993
+ );
8994
+ }
8995
+ }
8996
+ if (allTransactItems.length > MAX_TRANSACTION_ITEMS) {
8997
+ throw new StandardError(
8998
+ StandardErrorCode.TRANSACTION_ITEM_LIMIT_EXCEEDED,
8999
+ `Transaction contains ${allTransactItems.length} items, exceeds limit of ${MAX_TRANSACTION_ITEMS}`
9000
+ );
9001
+ }
9002
+ try {
9003
+ yield this.dynamodbClient.transactWriteItems({
9004
+ TransactItems: allTransactItems
9005
+ });
9006
+ } catch (err) {
9007
+ if (err instanceof TransactionCanceledException5) {
9008
+ throw new StandardError(
9009
+ StandardErrorCode.TRANSACTION_FAILED,
9010
+ "Transaction failed",
9011
+ err,
9012
+ {
9013
+ reasons: (_a = err.CancellationReasons) == null ? void 0 : _a.map((r, i) => ({
9014
+ index: i,
9015
+ code: r.Code,
9016
+ message: r.Message
9017
+ }))
9018
+ }
9019
+ );
9020
+ }
9021
+ throw err;
9022
+ }
9023
+ const readPromises = resultEntries.map((entry) => __async(this, null, function* () {
9024
+ if ((entry.operation === "updateEntity" || entry.operation === "adjustEntity") && !entry.data) {
9025
+ try {
9026
+ const entity = yield this.entityRepository.getEntity(
9027
+ entry.entityType,
9028
+ entry.entityId
9029
+ );
9030
+ entry.data = entity.data;
9031
+ } catch (e) {
9032
+ }
9033
+ }
9034
+ }));
9035
+ yield Promise.all(readPromises);
9036
+ yield Promise.allSettled(pendingEvents.map((ev) => this.publishEvent(ev)));
9037
+ return { results: resultEntries };
9038
+ });
9039
+ }
9040
+ buildCreateItems(op) {
9041
+ return __async(this, null, function* () {
9042
+ const config = this.EntityConfig[op.entityType];
9043
+ if (!config) {
9044
+ throw new StandardError(
9045
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9046
+ `Unknown entity type: '${op.entityType}'`
9047
+ );
9048
+ }
9049
+ const entitySchema = config.createSchema || config.baseSchema;
9050
+ if (!entitySchema) {
9051
+ throw new StandardError(
9052
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9053
+ `No schema defined for entity type: '${op.entityType}'`
9054
+ );
9055
+ }
9056
+ if (config.finalSchema) {
9057
+ config.finalSchema.parse(op.payload);
9058
+ }
9059
+ const parsedPayload = entitySchema.parse(
9060
+ op.payload
9061
+ );
9062
+ const currentDatetime = /* @__PURE__ */ new Date();
9063
+ const entity = new Entity(
9064
+ op.entityType,
9065
+ op.entityId || ulid6(),
9066
+ parsedPayload,
9067
+ currentDatetime,
9068
+ currentDatetime
9069
+ );
9070
+ const uniqueFields = config.uniqueFields || [];
9071
+ const uniqueFieldValues = {};
9072
+ for (const field of uniqueFields) {
9073
+ if (!(field in parsedPayload)) continue;
9074
+ const value = parsedPayload[field];
9075
+ if (typeof value !== "string") {
9076
+ throw new StandardError(
9077
+ StandardErrorCode.INVALID_UNIQUE_VALUE_TYPE,
9078
+ `Invalid type. ${field} is not a 'string'.`
9079
+ );
9080
+ }
9081
+ uniqueFieldValues[field] = value;
9082
+ }
9083
+ const items = this.entityRepository.createEntityTransactItems(entity, {
9084
+ uniqueFieldValues
9085
+ });
9086
+ return { items, entity };
9087
+ });
9088
+ }
9089
+ buildUpdateItem(op) {
9090
+ return __async(this, null, function* () {
9091
+ const config = this.EntityConfig[op.entityType];
9092
+ if (!config) {
9093
+ throw new StandardError(
9094
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9095
+ `Unknown entity type: '${op.entityType}'`
9096
+ );
9097
+ }
9098
+ const uniqueFields = config.uniqueFields || [];
9099
+ for (const field of uniqueFields) {
9100
+ if (field in op.payload) {
9101
+ throw new StandardError(
9102
+ StandardErrorCode.TRANSACTION_UNIQUE_FIELD_UPDATE,
9103
+ `Cannot update unique field '${field}' within a transaction. Use a standalone updateEntity call instead.`
9104
+ );
9105
+ }
9106
+ }
9107
+ const entitySchema = config.baseSchema;
9108
+ if (!entitySchema) {
9109
+ throw new StandardError(
9110
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9111
+ `No schema defined for entity type: '${op.entityType}'`
9112
+ );
9113
+ }
9114
+ const parsedPayload = entitySchema.partial().parse(op.payload);
9115
+ const currentDatetime = (/* @__PURE__ */ new Date()).toISOString();
9116
+ const toUpdateExpressions = this.entityRepository.toUpdate({
9117
+ updatedAt: currentDatetime,
9118
+ data: parsedPayload
9119
+ });
9120
+ let conditionOpts;
9121
+ if (op.condition) {
9122
+ const updateConditions = config.updateConditions;
9123
+ if (!updateConditions) {
9124
+ throw new StandardError(
9125
+ StandardErrorCode.INVALID_CONDITION,
9126
+ `Entity '${op.entityType}' has no updateConditions defined`
9127
+ );
9128
+ }
9129
+ conditionOpts = yield resolveUpdateCondition({
9130
+ conditionName: op.condition,
9131
+ conditions: updateConditions,
9132
+ getEntityData: () => __async(this, null, function* () {
9133
+ var _a;
9134
+ const entity2 = yield this.entityRepository.getEntity(
9135
+ op.entityType,
9136
+ op.entityId
9137
+ );
9138
+ return (_a = entity2 == null ? void 0 : entity2.data) != null ? _a : {};
9139
+ })
9140
+ });
9141
+ }
9142
+ const entity = new Entity(op.entityType, op.entityId);
9143
+ const item = {
9144
+ Update: {
9145
+ TableName: this.entityRepository.TABLE_NAME,
9146
+ Key: entity.keys(),
9147
+ ConditionExpression: (conditionOpts == null ? void 0 : conditionOpts.ConditionExpression) || "attribute_exists(PK)",
9148
+ UpdateExpression: toUpdateExpressions.UpdateExpression,
9149
+ ExpressionAttributeNames: __spreadValues(__spreadValues({}, toUpdateExpressions.ExpressionAttributeNames), conditionOpts == null ? void 0 : conditionOpts.ExpressionAttributeNames),
9150
+ ExpressionAttributeValues: __spreadValues(__spreadValues({}, toUpdateExpressions.ExpressionAttributeValues), conditionOpts == null ? void 0 : conditionOpts.ExpressionAttributeValues)
9151
+ }
9152
+ };
9153
+ return { item, updatedAt: currentDatetime };
9154
+ });
9155
+ }
9156
+ buildAdjustItem(op) {
9157
+ return __async(this, null, function* () {
9158
+ const config = this.EntityConfig[op.entityType];
9159
+ if (!config) {
9160
+ throw new StandardError(
9161
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9162
+ `Unknown entity type: '${op.entityType}'`
9163
+ );
9164
+ }
9165
+ for (const [key, value] of Object.entries(op.adjustments)) {
9166
+ if (typeof value !== "number" || !Number.isFinite(value)) {
9167
+ throw new StandardError(
9168
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9169
+ `Adjustment field "${key}" must be a finite number`
9170
+ );
9171
+ }
9172
+ }
9173
+ const {
9174
+ UpdateExpression,
9175
+ ExpressionAttributeNames,
9176
+ ExpressionAttributeValues
9177
+ } = this.entityRepository.toAdjustUpdate(op.adjustments);
9178
+ const currentDatetime = (/* @__PURE__ */ new Date()).toISOString();
9179
+ ExpressionAttributeNames["#updatedAt"] = "updatedAt";
9180
+ ExpressionAttributeValues[":updatedAt"] = { S: currentDatetime };
9181
+ const fullUpdateExpression = `${UpdateExpression}, #updatedAt = :updatedAt`;
9182
+ let conditionOpts;
9183
+ const adjustmentConditions = config.adjustmentConditions;
9184
+ if (adjustmentConditions) {
9185
+ if (!op.condition) {
9186
+ throw new StandardError(
9187
+ StandardErrorCode.INVALID_CONDITION,
9188
+ `Entity '${op.entityType}' has adjustmentConditions defined; condition is required`
9189
+ );
9190
+ }
9191
+ conditionOpts = yield resolveAdjustmentCondition({
9192
+ conditionName: op.condition,
9193
+ conditions: adjustmentConditions,
9194
+ adjustments: op.adjustments,
9195
+ getEntityData: () => __async(this, null, function* () {
9196
+ var _a;
9197
+ const entity2 = yield this.entityRepository.getEntity(
9198
+ op.entityType,
9199
+ op.entityId
9200
+ );
9201
+ return (_a = entity2 == null ? void 0 : entity2.data) != null ? _a : {};
9202
+ })
9203
+ });
9204
+ }
9205
+ const entity = new Entity(op.entityType, op.entityId);
9206
+ const item = {
9207
+ Update: {
9208
+ TableName: this.entityRepository.TABLE_NAME,
9209
+ Key: entity.keys(),
9210
+ UpdateExpression: fullUpdateExpression,
9211
+ ConditionExpression: (conditionOpts == null ? void 0 : conditionOpts.ConditionExpression) || "attribute_exists(PK)",
9212
+ ExpressionAttributeNames: __spreadValues(__spreadValues({}, ExpressionAttributeNames), conditionOpts == null ? void 0 : conditionOpts.ExpressionAttributeNames),
9213
+ ExpressionAttributeValues: __spreadValues(__spreadValues({}, ExpressionAttributeValues), conditionOpts == null ? void 0 : conditionOpts.ExpressionAttributeValues)
9214
+ }
9215
+ };
9216
+ return { item, updatedAt: currentDatetime };
9217
+ });
9218
+ }
9219
+ buildDeleteItem(op) {
9220
+ const config = this.EntityConfig[op.entityType];
9221
+ if (!config) {
9222
+ throw new StandardError(
9223
+ StandardErrorCode.INVALID_ENTITY_TYPE,
9224
+ `Unknown entity type: '${op.entityType}'`
9225
+ );
9226
+ }
9227
+ const entity = new Entity(op.entityType, op.entityId);
9228
+ return {
9229
+ Delete: {
9230
+ TableName: this.entityRepository.TABLE_NAME,
9231
+ Key: entity.keys(),
9232
+ ConditionExpression: "attribute_exists(PK)"
9233
+ }
9234
+ };
9235
+ }
9236
+ collectCreateEvents(entity, payload, accountId) {
9237
+ var _a, _b;
9238
+ const events = [];
9239
+ const publishedAt = entity.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
9240
+ const config = this.EntityConfig[entity.entityType];
9241
+ const mutualSchema = (_a = config == null ? void 0 : config.mutual) == null ? void 0 : _a.mutualSchema;
9242
+ if (mutualSchema) {
9243
+ const parsedMutualPayload = mutualSchema.parse(payload);
9244
+ if (parsedMutualPayload) {
9245
+ for (const [fieldKey, fieldConfig] of Object.entries(
9246
+ ((_b = config.mutual) == null ? void 0 : _b.mutualFields) || {}
9247
+ )) {
9248
+ const toMutualIds = fieldConfig.toMutualIds;
9249
+ const mutualPayload = parsedMutualPayload[fieldKey];
9250
+ if (!mutualPayload) continue;
9251
+ events.push({
9252
+ event: EVENT.CORE.ENTITY_MUTUAL_TO_CREATE,
9253
+ payload: {
9254
+ byEntityType: entity.entityType,
9255
+ byEntityId: entity.entityId,
9256
+ entityType: fieldConfig.entityType,
9257
+ field: fieldKey,
9258
+ mutualIds: toMutualIds ? toMutualIds(mutualPayload) : mutualPayload,
9259
+ customContext: toMutualIds ? mutualPayload : {},
9260
+ publishedAt
9261
+ }
9262
+ });
9263
+ }
9264
+ }
9265
+ }
9266
+ events.push({
9267
+ event: EVENT.CORE.ENTITY_CREATED,
9268
+ payload: {
9269
+ entityType: entity.entityType,
9270
+ entityId: entity.entityId,
9271
+ data: entity.data,
9272
+ createdByAccountId: accountId,
9273
+ publishedAt
9274
+ }
9275
+ });
9276
+ return events;
9277
+ }
9278
+ collectUpdateEvents(op, updatedAt, accountId) {
9279
+ var _a, _b;
9280
+ const events = [];
9281
+ const config = this.EntityConfig[op.entityType];
9282
+ const mutualSchema = (_a = config == null ? void 0 : config.mutual) == null ? void 0 : _a.mutualSchema;
9283
+ if (mutualSchema) {
9284
+ const parsedMutualPayload = mutualSchema.parse(op.payload);
9285
+ if (parsedMutualPayload) {
9286
+ for (const [fieldKey, fieldConfig] of Object.entries(
9287
+ ((_b = config.mutual) == null ? void 0 : _b.mutualFields) || {}
9288
+ )) {
9289
+ const toMutualIds = fieldConfig.toMutualIds;
9290
+ const mutualPayload = parsedMutualPayload[fieldKey];
9291
+ if (!mutualPayload) continue;
9292
+ events.push({
9293
+ event: EVENT.CORE.ENTITY_MUTUAL_TO_UPDATE,
9294
+ payload: {
9295
+ byEntityType: op.entityType,
9296
+ byEntityId: op.entityId,
9297
+ entityType: fieldConfig.entityType,
9298
+ field: fieldKey,
9299
+ mutualIds: toMutualIds ? toMutualIds(mutualPayload) : mutualPayload,
9300
+ customContext: toMutualIds ? mutualPayload : {},
9301
+ publishedAt: updatedAt
9302
+ }
9303
+ });
9304
+ }
9305
+ }
9306
+ }
9307
+ events.push({
9308
+ event: EVENT.CORE.ENTITY_UPDATED,
9309
+ payload: {
9310
+ entityType: op.entityType,
9311
+ entityId: op.entityId,
9312
+ updatedByAccountId: accountId,
9313
+ publishedAt: updatedAt
9314
+ }
9315
+ });
9316
+ return events;
9317
+ }
9318
+ };
9319
+
8002
9320
  // services/DependencyContainer.ts
8003
9321
  var DependencyContainer = class {
8004
9322
  constructor(config) {
@@ -8091,6 +9409,13 @@ var DependencyContainer = class {
8091
9409
  this.dynamodbClient
8092
9410
  );
8093
9411
  }
9412
+ get websocketRepository() {
9413
+ return this.createCachedInstance(
9414
+ WebSocketRepository,
9415
+ this.coreTable,
9416
+ this.dynamodbClient
9417
+ );
9418
+ }
8094
9419
  get getEntityController() {
8095
9420
  return this.createCachedInstance(
8096
9421
  GetEntityController,
@@ -8174,6 +9499,62 @@ var DependencyContainer = class {
8174
9499
  get listTagsController() {
8175
9500
  return this.createCachedInstance(ListTagsController, this.tagRepository);
8176
9501
  }
9502
+ get transactionService() {
9503
+ return this.createCachedInstance(
9504
+ TransactionService,
9505
+ this.config.EntityConfig,
9506
+ this.config.EmailAuthEnabledEntities,
9507
+ this.entityRepository,
9508
+ this.dynamodbClient,
9509
+ this.publishEvent,
9510
+ this.entityServiceLifeCycle,
9511
+ this.eventUtils
9512
+ );
9513
+ }
9514
+ get executeTransactionController() {
9515
+ return this.createCachedInstance(
9516
+ ExecuteTransactionController,
9517
+ this.transactionService
9518
+ );
9519
+ }
9520
+ get createTicketController() {
9521
+ return this.createCachedInstance(CreateTicketController, this);
9522
+ }
9523
+ };
9524
+
9525
+ // helpers/transactional.ts
9526
+ var transactional = {
9527
+ createEntity: (entityType, payload) => {
9528
+ const _a = payload, { entityId } = _a, rest = __objRest(_a, ["entityId"]);
9529
+ return __spreadValues({
9530
+ operation: "createEntity",
9531
+ entityType,
9532
+ payload: rest
9533
+ }, entityId && { entityId });
9534
+ },
9535
+ updateEntity: (entityType, entityId, payload) => {
9536
+ const _a = payload, { $condition } = _a, rest = __objRest(_a, ["$condition"]);
9537
+ return __spreadValues({
9538
+ operation: "updateEntity",
9539
+ entityType,
9540
+ entityId,
9541
+ payload: rest
9542
+ }, $condition && { condition: $condition });
9543
+ },
9544
+ adjustEntity: (entityType, entityId, adjustments) => {
9545
+ const _a = adjustments, { $condition } = _a, rest = __objRest(_a, ["$condition"]);
9546
+ return __spreadValues({
9547
+ operation: "adjustEntity",
9548
+ entityType,
9549
+ entityId,
9550
+ adjustments: rest
9551
+ }, $condition && { condition: $condition });
9552
+ },
9553
+ deleteEntity: (entityType, entityId) => ({
9554
+ operation: "deleteEntity",
9555
+ entityType,
9556
+ entityId
9557
+ })
8177
9558
  };
8178
9559
 
8179
9560
  // index.ts
@@ -8189,6 +9570,10 @@ var CoreFactory = class {
8189
9570
  this.prejoinProcessor = handler3(dependencyContainer);
8190
9571
  this.tagProcessor = handler5(dependencyContainer);
8191
9572
  this.appHandler = appHandler(dependencyContainer);
9573
+ this.wsConnect = connect(dependencyContainer);
9574
+ this.wsDisconnect = disconnect(dependencyContainer);
9575
+ this.wsDefault = $default(dependencyContainer);
9576
+ this.wsBroadcast = broadcast(dependencyContainer);
8192
9577
  }
8193
9578
  };
8194
9579
  var index_default = CoreFactory;
@@ -8204,6 +9589,8 @@ export {
8204
9589
  StandardError,
8205
9590
  StandardErrorCode,
8206
9591
  TagRepository,
9592
+ TransactionService,
9593
+ WebSocketRepository,
8207
9594
  appHandler,
8208
9595
  handler as createEntityProcessor,
8209
9596
  index_default as default,
@@ -8211,6 +9598,11 @@ export {
8211
9598
  handler3 as prejoinProcessor,
8212
9599
  handler4 as replicationProcessor,
8213
9600
  setupCommonRoutes,
8214
- handler5 as tagProcessor
9601
+ handler5 as tagProcessor,
9602
+ transactional,
9603
+ broadcast as wsBroadcast,
9604
+ connect as wsConnect,
9605
+ $default as wsDefault,
9606
+ disconnect as wsDisconnect
8215
9607
  };
8216
9608
  //# sourceMappingURL=index.js.map