@symbo.ls/sdk 3.2.3 → 3.2.7

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 (183) hide show
  1. package/README.md +141 -0
  2. package/dist/cjs/config/environment.js +94 -10
  3. package/dist/cjs/index.js +152 -12
  4. package/dist/cjs/services/AdminService.js +351 -0
  5. package/dist/cjs/services/AuthService.js +738 -305
  6. package/dist/cjs/services/BaseService.js +158 -6
  7. package/dist/cjs/services/BranchService.js +484 -0
  8. package/dist/cjs/services/CollabService.js +439 -116
  9. package/dist/cjs/services/DnsService.js +340 -0
  10. package/dist/cjs/services/FeatureFlagService.js +175 -0
  11. package/dist/cjs/services/FileService.js +201 -0
  12. package/dist/cjs/services/IntegrationService.js +538 -0
  13. package/dist/cjs/services/MetricsService.js +62 -0
  14. package/dist/cjs/services/PaymentService.js +271 -0
  15. package/dist/cjs/services/PlanService.js +426 -0
  16. package/dist/cjs/services/ProjectService.js +1207 -0
  17. package/dist/cjs/services/PullRequestService.js +503 -0
  18. package/dist/cjs/services/ScreenshotService.js +304 -0
  19. package/dist/cjs/services/SubscriptionService.js +396 -0
  20. package/dist/cjs/services/TrackingService.js +661 -0
  21. package/dist/cjs/services/WaitlistService.js +148 -0
  22. package/dist/cjs/services/index.js +60 -4
  23. package/dist/cjs/state/RootStateManager.js +2 -23
  24. package/dist/cjs/state/rootEventBus.js +9 -0
  25. package/dist/cjs/utils/CollabClient.js +78 -12
  26. package/dist/cjs/utils/TokenManager.js +16 -3
  27. package/dist/cjs/utils/changePreprocessor.js +199 -0
  28. package/dist/cjs/utils/jsonDiff.js +46 -4
  29. package/dist/cjs/utils/ordering.js +309 -0
  30. package/dist/cjs/utils/services.js +285 -128
  31. package/dist/cjs/utils/validation.js +0 -3
  32. package/dist/esm/config/environment.js +94 -10
  33. package/dist/esm/index.js +47862 -18248
  34. package/dist/esm/services/AdminService.js +1132 -0
  35. package/dist/esm/services/AuthService.js +1493 -386
  36. package/dist/esm/services/BaseService.js +757 -6
  37. package/dist/esm/services/BranchService.js +1265 -0
  38. package/dist/esm/services/CollabService.js +24956 -16089
  39. package/dist/esm/services/DnsService.js +1121 -0
  40. package/dist/esm/services/FeatureFlagService.js +956 -0
  41. package/dist/esm/services/FileService.js +982 -0
  42. package/dist/esm/services/IntegrationService.js +1319 -0
  43. package/dist/esm/services/MetricsService.js +843 -0
  44. package/dist/esm/services/PaymentService.js +1052 -0
  45. package/dist/esm/services/PlanService.js +1207 -0
  46. package/dist/esm/services/ProjectService.js +2526 -0
  47. package/dist/esm/services/PullRequestService.js +1284 -0
  48. package/dist/esm/services/ScreenshotService.js +1085 -0
  49. package/dist/esm/services/SubscriptionService.js +1177 -0
  50. package/dist/esm/services/TrackingService.js +18454 -0
  51. package/dist/esm/services/WaitlistService.js +929 -0
  52. package/dist/esm/services/index.js +47373 -18027
  53. package/dist/esm/state/RootStateManager.js +11 -23
  54. package/dist/esm/state/rootEventBus.js +9 -0
  55. package/dist/esm/utils/CollabClient.js +17526 -16120
  56. package/dist/esm/utils/TokenManager.js +16 -3
  57. package/dist/esm/utils/changePreprocessor.js +542 -0
  58. package/dist/esm/utils/jsonDiff.js +958 -43
  59. package/dist/esm/utils/ordering.js +291 -0
  60. package/dist/esm/utils/services.js +285 -128
  61. package/dist/esm/utils/validation.js +116 -50
  62. package/dist/node/config/environment.js +94 -10
  63. package/dist/node/index.js +183 -16
  64. package/dist/node/services/AdminService.js +332 -0
  65. package/dist/node/services/AuthService.js +742 -310
  66. package/dist/node/services/BaseService.js +148 -6
  67. package/dist/node/services/BranchService.js +465 -0
  68. package/dist/node/services/CollabService.js +439 -116
  69. package/dist/node/services/DnsService.js +321 -0
  70. package/dist/node/services/FeatureFlagService.js +156 -0
  71. package/dist/node/services/FileService.js +182 -0
  72. package/dist/node/services/IntegrationService.js +519 -0
  73. package/dist/node/services/MetricsService.js +43 -0
  74. package/dist/node/services/PaymentService.js +252 -0
  75. package/dist/node/services/PlanService.js +407 -0
  76. package/dist/node/services/ProjectService.js +1188 -0
  77. package/dist/node/services/PullRequestService.js +484 -0
  78. package/dist/node/services/ScreenshotService.js +285 -0
  79. package/dist/node/services/SubscriptionService.js +377 -0
  80. package/dist/node/services/TrackingService.js +632 -0
  81. package/dist/node/services/WaitlistService.js +129 -0
  82. package/dist/node/services/index.js +60 -4
  83. package/dist/node/state/RootStateManager.js +2 -23
  84. package/dist/node/state/rootEventBus.js +9 -0
  85. package/dist/node/utils/CollabClient.js +77 -11
  86. package/dist/node/utils/TokenManager.js +16 -3
  87. package/dist/node/utils/changePreprocessor.js +180 -0
  88. package/dist/node/utils/jsonDiff.js +46 -4
  89. package/dist/node/utils/ordering.js +290 -0
  90. package/dist/node/utils/services.js +285 -128
  91. package/dist/node/utils/validation.js +0 -3
  92. package/package.json +30 -18
  93. package/src/config/environment.js +95 -10
  94. package/src/index.js +190 -23
  95. package/src/services/AdminService.js +374 -0
  96. package/src/services/AuthService.js +874 -328
  97. package/src/services/BaseService.js +166 -6
  98. package/src/services/BranchService.js +536 -0
  99. package/src/services/CollabService.js +557 -148
  100. package/src/services/DnsService.js +366 -0
  101. package/src/services/FeatureFlagService.js +174 -0
  102. package/src/services/FileService.js +213 -0
  103. package/src/services/IntegrationService.js +548 -0
  104. package/src/services/MetricsService.js +40 -0
  105. package/src/services/PaymentService.js +287 -0
  106. package/src/services/PlanService.js +468 -0
  107. package/src/services/ProjectService.js +1366 -0
  108. package/src/services/PullRequestService.js +537 -0
  109. package/src/services/ScreenshotService.js +258 -0
  110. package/src/services/SubscriptionService.js +425 -0
  111. package/src/services/TrackingService.js +853 -0
  112. package/src/services/WaitlistService.js +130 -0
  113. package/src/services/index.js +79 -5
  114. package/src/services/tests/BranchService/createBranch.test.js +153 -0
  115. package/src/services/tests/BranchService/deleteBranch.test.js +173 -0
  116. package/src/services/tests/BranchService/getBranchChanges.test.js +146 -0
  117. package/src/services/tests/BranchService/listBranches.test.js +87 -0
  118. package/src/services/tests/BranchService/mergeBranch.test.js +210 -0
  119. package/src/services/tests/BranchService/publishVersion.test.js +183 -0
  120. package/src/services/tests/BranchService/renameBranch.test.js +240 -0
  121. package/src/services/tests/BranchService/resetBranch.test.js +152 -0
  122. package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +67 -0
  123. package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +75 -0
  124. package/src/services/tests/FileService/createFileFormData.test.js +74 -0
  125. package/src/services/tests/FileService/getFileUrl.test.js +69 -0
  126. package/src/services/tests/FileService/updateProjectIcon.test.js +109 -0
  127. package/src/services/tests/FileService/uploadDocument.test.js +36 -0
  128. package/src/services/tests/FileService/uploadFile.test.js +78 -0
  129. package/src/services/tests/FileService/uploadFileWithValidation.test.js +114 -0
  130. package/src/services/tests/FileService/uploadImage.test.js +36 -0
  131. package/src/services/tests/FileService/uploadMultipleFiles.test.js +111 -0
  132. package/src/services/tests/FileService/validateFile.test.js +63 -0
  133. package/src/services/tests/PlanService/createPlan.test.js +104 -0
  134. package/src/services/tests/PlanService/createPlanWithValidation.test.js +523 -0
  135. package/src/services/tests/PlanService/deletePlan.test.js +92 -0
  136. package/src/services/tests/PlanService/getActivePlans.test.js +123 -0
  137. package/src/services/tests/PlanService/getAdminPlans.test.js +84 -0
  138. package/src/services/tests/PlanService/getPlan.test.js +50 -0
  139. package/src/services/tests/PlanService/getPlanByKey.test.js +109 -0
  140. package/src/services/tests/PlanService/getPlanWithValidation.test.js +85 -0
  141. package/src/services/tests/PlanService/getPlans.test.js +53 -0
  142. package/src/services/tests/PlanService/getPlansByPriceRange.test.js +109 -0
  143. package/src/services/tests/PlanService/getPlansWithValidation.test.js +48 -0
  144. package/src/services/tests/PlanService/initializePlans.test.js +75 -0
  145. package/src/services/tests/PlanService/updatePlan.test.js +111 -0
  146. package/src/services/tests/PlanService/updatePlanWithValidation.test.js +556 -0
  147. package/src/state/RootStateManager.js +37 -32
  148. package/src/state/rootEventBus.js +19 -0
  149. package/src/utils/CollabClient.js +99 -12
  150. package/src/utils/TokenManager.js +20 -3
  151. package/src/utils/changePreprocessor.js +239 -0
  152. package/src/utils/jsonDiff.js +40 -5
  153. package/src/utils/ordering.js +271 -0
  154. package/src/utils/services.js +306 -139
  155. package/src/utils/validation.js +0 -3
  156. package/dist/cjs/services/AIService.js +0 -155
  157. package/dist/cjs/services/BasedService.js +0 -1185
  158. package/dist/cjs/services/CoreService.js +0 -2295
  159. package/dist/cjs/services/SocketService.js +0 -309
  160. package/dist/cjs/services/SymstoryService.js +0 -571
  161. package/dist/cjs/utils/basedQuerys.js +0 -181
  162. package/dist/cjs/utils/symstoryClient.js +0 -259
  163. package/dist/esm/services/AIService.js +0 -185
  164. package/dist/esm/services/BasedService.js +0 -5262
  165. package/dist/esm/services/CoreService.js +0 -2827
  166. package/dist/esm/services/SocketService.js +0 -456
  167. package/dist/esm/services/SymstoryService.js +0 -7025
  168. package/dist/esm/utils/basedQuerys.js +0 -163
  169. package/dist/esm/utils/symstoryClient.js +0 -354
  170. package/dist/node/services/AIService.js +0 -136
  171. package/dist/node/services/BasedService.js +0 -1156
  172. package/dist/node/services/CoreService.js +0 -2266
  173. package/dist/node/services/SocketService.js +0 -280
  174. package/dist/node/services/SymstoryService.js +0 -542
  175. package/dist/node/utils/basedQuerys.js +0 -162
  176. package/dist/node/utils/symstoryClient.js +0 -230
  177. package/src/services/AIService.js +0 -150
  178. package/src/services/BasedService.js +0 -1302
  179. package/src/services/CoreService.js +0 -2548
  180. package/src/services/SocketService.js +0 -336
  181. package/src/services/SymstoryService.js +0 -649
  182. package/src/utils/basedQuerys.js +0 -164
  183. package/src/utils/symstoryClient.js +0 -252
@@ -0,0 +1,2526 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/config/environment.js
6
+ import { isDevelopment } from "@domql/utils";
7
+ var CONFIG = {
8
+ // Common defaults for all environments
9
+ common: {
10
+ // NOTE: Google client id for google auth, need to configure URLs for each environment in Google console
11
+ googleClientId: "686286207466-bvd2fqs31rlm64fgich7rtpnc8ns2tqg.apps.googleusercontent.com",
12
+ // Feature toggles that apply across all environments by default
13
+ features: {
14
+ newUserOnboarding: true,
15
+ betaFeatures: false,
16
+ // Tracking is enabled by default unless overridden per environment
17
+ trackingEnabled: true
18
+ }
19
+ },
20
+ // Environment-specific configurations
21
+ local: {
22
+ // local
23
+ socketUrl: "http://localhost:8080",
24
+ // For socket api
25
+ apiUrl: "http://localhost:8080",
26
+ // For server api
27
+ basedEnv: "development",
28
+ // For based api
29
+ basedProject: "platform-v2-sm",
30
+ // For based api
31
+ basedOrg: "symbols",
32
+ // For based api
33
+ githubClientId: "Ov23liAFrsR0StbAO6PO",
34
+ // For github api
35
+ grafanaUrl: "",
36
+ // For grafana tracing
37
+ grafanaAppName: "Symbols Localhost",
38
+ // Environment-specific feature toggles (override common)
39
+ features: {
40
+ // Disable tracking by default on localhost/dev machines
41
+ trackingEnabled: false,
42
+ // Enable beta features in local dev
43
+ betaFeatures: true,
44
+ // Preserve common defaults explicitly for local
45
+ newUserOnboarding: true
46
+ },
47
+ typesenseCollectionName: "docs",
48
+ typesenseApiKey: "vZya3L2zpq8L6iI5WWMUZJZABvT63VDb",
49
+ typesenseHost: "localhost",
50
+ typesensePort: "8108",
51
+ typesenseProtocol: "http"
52
+ },
53
+ development: {
54
+ socketUrl: "https://dev.api.symbols.app",
55
+ apiUrl: "https://dev.api.symbols.app",
56
+ githubClientId: "Ov23liHxyWFBxS8f1gnF",
57
+ grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/7a3ba473cee2025c68513667024316b8",
58
+ // For grafana tracing
59
+ grafanaAppName: "Symbols Dev",
60
+ typesenseCollectionName: "docs",
61
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
62
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
63
+ typesensePort: "443",
64
+ typesenseProtocol: "https"
65
+ },
66
+ testing: {
67
+ socketUrl: "https://test.api.symbols.app",
68
+ apiUrl: "https://test.api.symbols.app",
69
+ basedEnv: "testing",
70
+ basedProject: "platform-v2-sm",
71
+ basedOrg: "symbols",
72
+ githubClientId: "Ov23liHxyWFBxS8f1gnF",
73
+ grafanaUrl: "",
74
+ // For grafana tracing
75
+ grafanaAppName: "Symbols Test",
76
+ typesenseCollectionName: "docs",
77
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
78
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
79
+ typesensePort: "443",
80
+ typesenseProtocol: "https"
81
+ },
82
+ upcoming: {
83
+ socketUrl: "https://upcoming.api.symbols.app",
84
+ apiUrl: "https://upcoming.api.symbols.app",
85
+ githubClientId: "Ov23liWF7NvdZ056RV5J",
86
+ grafanaUrl: "",
87
+ // For grafana tracing
88
+ grafanaAppName: "Symbols Upcoming",
89
+ typesenseCollectionName: "docs",
90
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
91
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
92
+ typesensePort: "443",
93
+ typesenseProtocol: "https"
94
+ },
95
+ staging: {
96
+ socketUrl: "https://staging.api.symbols.app",
97
+ apiUrl: "https://staging.api.symbols.app",
98
+ basedEnv: "staging",
99
+ basedProject: "platform-v2-sm",
100
+ basedOrg: "symbols",
101
+ githubClientId: "Ov23ligwZDQVD0VfuWNa",
102
+ grafanaUrl: "",
103
+ // For grafana tracing
104
+ grafanaAppName: "Symbols Staging",
105
+ typesenseCollectionName: "docs",
106
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
107
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
108
+ typesensePort: "443",
109
+ typesenseProtocol: "https"
110
+ },
111
+ preview: {
112
+ socketUrl: "https://api.symbols.app",
113
+ apiUrl: "https://api.symbols.app",
114
+ basedEnv: "production",
115
+ basedProject: "platform-v2-sm",
116
+ basedOrg: "symbols",
117
+ githubClientId: "Ov23liFAlOEIXtX3dBtR",
118
+ grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/5c1089f3c3eea4ec5658e05c3f53baae",
119
+ // For grafana tracing
120
+ grafanaAppName: "Symbols Preview",
121
+ typesenseCollectionName: "docs",
122
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
123
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
124
+ typesensePort: "443",
125
+ typesenseProtocol: "https"
126
+ },
127
+ production: {
128
+ socketUrl: "https://api.symbols.app",
129
+ apiUrl: "https://api.symbols.app",
130
+ basedEnv: "production",
131
+ basedProject: "platform-v2-sm",
132
+ basedOrg: "symbols",
133
+ githubClientId: "Ov23liFAlOEIXtX3dBtR",
134
+ grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/5c1089f3c3eea4ec5658e05c3f53baae",
135
+ // For grafana tracing
136
+ grafanaAppName: "Symbols",
137
+ typesenseCollectionName: "docs",
138
+ typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
139
+ typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
140
+ typesensePort: "443",
141
+ typesenseProtocol: "https"
142
+ }
143
+ };
144
+ var getEnvironment = () => {
145
+ const env = process.env.SYMBOLS_APP_ENV || process.env.NODE_ENV;
146
+ if (!CONFIG[env]) {
147
+ throw new Error(`Unknown environment "${env}"`);
148
+ }
149
+ return env;
150
+ };
151
+ var getConfig = () => {
152
+ try {
153
+ const env = getEnvironment();
154
+ const envConfig = { ...CONFIG.common, ...CONFIG[env] };
155
+ const finalConfig = {
156
+ ...envConfig,
157
+ // Deep-merge feature flags so env-specific overrides don't drop common defaults
158
+ features: {
159
+ ...CONFIG.common.features || {},
160
+ ...CONFIG[env] && CONFIG[env].features || {}
161
+ },
162
+ socketUrl: process.env.SYMBOLS_APP_SOCKET_URL || envConfig.socketUrl,
163
+ apiUrl: process.env.SYMBOLS_APP_API_URL || envConfig.apiUrl,
164
+ basedEnv: process.env.SYMBOLS_APP_BASED_ENV || envConfig.basedEnv,
165
+ basedProject: process.env.SYMBOLS_APP_BASED_PROJECT || envConfig.basedProject,
166
+ basedOrg: process.env.SYMBOLS_APP_BASED_ORG || envConfig.basedOrg,
167
+ githubClientId: process.env.SYMBOLS_APP_GITHUB_CLIENT_ID || envConfig.githubClientId,
168
+ grafanaUrl: process.env.SYMBOLS_APP_GRAFANA_URL || envConfig.grafanaUrl,
169
+ typesenseCollectionName: process.env.TYPESENSE_COLLECTION_NAME || envConfig.typesenseCollectionName,
170
+ typesenseApiKey: process.env.TYPESENSE_API_KEY || envConfig.typesenseApiKey,
171
+ typesenseHost: process.env.TYPESENSE_HOST || envConfig.typesenseHost,
172
+ typesensePort: process.env.TYPESENSE_PORT || envConfig.typesensePort,
173
+ typesenseProtocol: process.env.TYPESENSE_PROTOCOL || envConfig.typesenseProtocol,
174
+ isDevelopment: isDevelopment(env),
175
+ isTesting: env === "testing",
176
+ isStaging: env === "staging",
177
+ isPreview: env === "preview",
178
+ isProduction: env === "production"
179
+ // Store all environment variables for potential future use
180
+ };
181
+ const requiredFields = [
182
+ "socketUrl",
183
+ "apiUrl",
184
+ "githubClientId",
185
+ "googleClientId"
186
+ ];
187
+ const missingFields = requiredFields.filter((field) => !finalConfig[field]);
188
+ if (missingFields.length > 0) {
189
+ console.error(
190
+ `Missing required configuration: ${missingFields.join(", ")}`
191
+ );
192
+ }
193
+ if (finalConfig.isDevelopment) {
194
+ console.warn(
195
+ "environment in SDK:",
196
+ env || process.env.NODE_ENV || process.env.NODE_ENV
197
+ );
198
+ console.log(finalConfig);
199
+ } else if (global.window) {
200
+ global.window.finalConfig = finalConfig;
201
+ }
202
+ return finalConfig;
203
+ } catch (error) {
204
+ console.error("Failed to load environment configuration:", error);
205
+ return {
206
+ ...CONFIG.development
207
+ };
208
+ }
209
+ };
210
+ var environment_default = getConfig();
211
+
212
+ // src/utils/TokenManager.js
213
+ var TokenManager = class {
214
+ constructor(options = {}) {
215
+ /**
216
+ * Memory storage fallback for server-side rendering
217
+ */
218
+ __publicField(this, "_memoryStorage", {
219
+ _data: {},
220
+ getItem: (key) => this._memoryStorage._data[key] || null,
221
+ setItem: (key, value) => {
222
+ this._memoryStorage._data[key] = value;
223
+ },
224
+ removeItem: (key) => {
225
+ delete this._memoryStorage._data[key];
226
+ },
227
+ clear: () => {
228
+ this._memoryStorage._data = {};
229
+ }
230
+ });
231
+ this.config = {
232
+ storagePrefix: "symbols_",
233
+ storageType: typeof window === "undefined" || process.env.NODE_ENV === "test" || process.env.NODE_ENV === "testing" ? "memory" : "localStorage",
234
+ // 'localStorage' | 'sessionStorage' | 'memory'
235
+ refreshBuffer: 60 * 1e3,
236
+ // Refresh 1 minute before expiry
237
+ maxRetries: 3,
238
+ apiUrl: options.apiUrl || "/api",
239
+ onTokenRefresh: options.onTokenRefresh || null,
240
+ onTokenExpired: options.onTokenExpired || null,
241
+ onTokenError: options.onTokenError || null,
242
+ ...options
243
+ };
244
+ this.tokens = {
245
+ accessToken: null,
246
+ refreshToken: null,
247
+ expiresAt: null,
248
+ expiresIn: null
249
+ };
250
+ this.refreshPromise = null;
251
+ this.refreshTimeout = null;
252
+ this.retryCount = 0;
253
+ this.loadTokens();
254
+ }
255
+ /**
256
+ * Storage keys
257
+ */
258
+ get storageKeys() {
259
+ return {
260
+ accessToken: `${this.config.storagePrefix}access_token`,
261
+ refreshToken: `${this.config.storagePrefix}refresh_token`,
262
+ expiresAt: `${this.config.storagePrefix}expires_at`,
263
+ expiresIn: `${this.config.storagePrefix}expires_in`
264
+ };
265
+ }
266
+ /**
267
+ * Get storage instance based on configuration
268
+ */
269
+ get storage() {
270
+ if (typeof window === "undefined") {
271
+ return this._memoryStorage;
272
+ }
273
+ const safeGetStorage = (provider) => {
274
+ try {
275
+ const storage = provider();
276
+ const testKey = `${this.config.storagePrefix}__tm_test__`;
277
+ storage.setItem(testKey, "1");
278
+ storage.removeItem(testKey);
279
+ return storage;
280
+ } catch {
281
+ return null;
282
+ }
283
+ };
284
+ const localStorageInstance = safeGetStorage(() => window.localStorage);
285
+ const sessionStorageInstance = safeGetStorage(() => window.sessionStorage);
286
+ switch (this.config.storageType) {
287
+ case "sessionStorage":
288
+ return sessionStorageInstance || this._memoryStorage;
289
+ case "memory":
290
+ return this._memoryStorage;
291
+ default:
292
+ return localStorageInstance || this._memoryStorage;
293
+ }
294
+ }
295
+ /**
296
+ * Set tokens and persist to storage
297
+ */
298
+ setTokens(tokenData) {
299
+ const {
300
+ access_token: accessToken,
301
+ refresh_token: refreshToken,
302
+ expires_in: expiresIn,
303
+ token_type: tokenType = "Bearer"
304
+ } = tokenData;
305
+ if (!accessToken) {
306
+ throw new Error("Access token is required");
307
+ }
308
+ const now = Date.now();
309
+ const expiresAt = expiresIn ? now + expiresIn * 1e3 : null;
310
+ this.tokens = {
311
+ accessToken,
312
+ refreshToken: refreshToken || this.tokens.refreshToken,
313
+ expiresAt,
314
+ expiresIn,
315
+ tokenType
316
+ };
317
+ this.saveTokens();
318
+ this.scheduleRefresh();
319
+ if (this.config.onTokenRefresh) {
320
+ this.config.onTokenRefresh(this.tokens);
321
+ }
322
+ return this.tokens;
323
+ }
324
+ /**
325
+ * Get current access token
326
+ */
327
+ getAccessToken() {
328
+ return this.tokens.accessToken;
329
+ }
330
+ /**
331
+ * Get current refresh token
332
+ */
333
+ getRefreshToken() {
334
+ return this.tokens.refreshToken;
335
+ }
336
+ /**
337
+ * Get authorization header value
338
+ */
339
+ getAuthHeader() {
340
+ const token = this.getAccessToken();
341
+ if (!token) {
342
+ return null;
343
+ }
344
+ return `${this.tokens.tokenType || "Bearer"} ${token}`;
345
+ }
346
+ /**
347
+ * Check if access token is valid and not expired
348
+ */
349
+ isAccessTokenValid() {
350
+ if (!this.tokens.accessToken) {
351
+ return false;
352
+ }
353
+ if (!this.tokens.expiresAt) {
354
+ return true;
355
+ }
356
+ const now = Date.now();
357
+ const isValid = now < this.tokens.expiresAt - this.config.refreshBuffer;
358
+ if (!isValid) {
359
+ console.log("[TokenManager] Access token is expired or near expiry:", {
360
+ now: new Date(now).toISOString(),
361
+ expiresAt: new Date(this.tokens.expiresAt).toISOString(),
362
+ refreshBuffer: this.config.refreshBuffer
363
+ });
364
+ }
365
+ return isValid;
366
+ }
367
+ /**
368
+ * Check if access token exists and is not expired (without refresh buffer)
369
+ */
370
+ isAccessTokenActuallyValid() {
371
+ if (!this.tokens.accessToken) {
372
+ return false;
373
+ }
374
+ if (!this.tokens.expiresAt) {
375
+ return true;
376
+ }
377
+ const now = Date.now();
378
+ return now < this.tokens.expiresAt;
379
+ }
380
+ /**
381
+ * Check if tokens exist (regardless of expiry)
382
+ */
383
+ hasTokens() {
384
+ return Boolean(this.tokens.accessToken);
385
+ }
386
+ /**
387
+ * Check if refresh token exists
388
+ */
389
+ hasRefreshToken() {
390
+ return Boolean(this.tokens.refreshToken);
391
+ }
392
+ /**
393
+ * Automatically refresh tokens if needed
394
+ */
395
+ async ensureValidToken() {
396
+ if (!this.hasTokens()) {
397
+ return null;
398
+ }
399
+ if (this.isAccessTokenValid()) {
400
+ return this.getAccessToken();
401
+ }
402
+ if (!this.hasRefreshToken()) {
403
+ this.clearTokens();
404
+ if (this.config.onTokenExpired) {
405
+ this.config.onTokenExpired();
406
+ }
407
+ return null;
408
+ }
409
+ try {
410
+ await this.refreshTokens();
411
+ return this.getAccessToken();
412
+ } catch (error) {
413
+ this.clearTokens();
414
+ if (this.config.onTokenError) {
415
+ this.config.onTokenError(error);
416
+ }
417
+ throw error;
418
+ }
419
+ }
420
+ /**
421
+ * Refresh access token using refresh token
422
+ */
423
+ async refreshTokens() {
424
+ if (this.refreshPromise) {
425
+ return this.refreshPromise;
426
+ }
427
+ if (!this.hasRefreshToken()) {
428
+ throw new Error("No refresh token available");
429
+ }
430
+ if (this.retryCount >= this.config.maxRetries) {
431
+ throw new Error("Max refresh retries exceeded");
432
+ }
433
+ this.refreshPromise = this._performRefresh();
434
+ try {
435
+ const result = await this.refreshPromise;
436
+ this.retryCount = 0;
437
+ return result;
438
+ } catch (error) {
439
+ this.retryCount++;
440
+ throw error;
441
+ } finally {
442
+ this.refreshPromise = null;
443
+ }
444
+ }
445
+ /**
446
+ * Perform the actual token refresh request
447
+ */
448
+ async _performRefresh() {
449
+ var _a;
450
+ const refreshToken = this.getRefreshToken();
451
+ const response = await fetch(`${this.config.apiUrl}/core/auth/refresh`, {
452
+ method: "POST",
453
+ headers: {
454
+ "Content-Type": "application/json"
455
+ },
456
+ body: JSON.stringify({ refreshToken })
457
+ });
458
+ if (!response.ok) {
459
+ const errorData = await response.json().catch(() => ({}));
460
+ throw new Error(errorData.message || `Token refresh failed: ${response.status}`);
461
+ }
462
+ const responseData = await response.json();
463
+ if (responseData.success && responseData.data && responseData.data.tokens) {
464
+ const { tokens } = responseData.data;
465
+ const tokenData = {
466
+ access_token: tokens.accessToken,
467
+ refresh_token: tokens.refreshToken,
468
+ expires_in: (_a = tokens.accessTokenExp) == null ? void 0 : _a.expiresIn,
469
+ token_type: "Bearer"
470
+ };
471
+ return this.setTokens(tokenData);
472
+ }
473
+ return this.setTokens(responseData);
474
+ }
475
+ /**
476
+ * Schedule automatic token refresh
477
+ */
478
+ scheduleRefresh() {
479
+ if (this.refreshTimeout) {
480
+ clearTimeout(this.refreshTimeout);
481
+ this.refreshTimeout = null;
482
+ }
483
+ if (!this.tokens.expiresAt || !this.hasRefreshToken()) {
484
+ return;
485
+ }
486
+ const now = Date.now();
487
+ const refreshTime = this.tokens.expiresAt - this.config.refreshBuffer;
488
+ const delay = Math.max(0, refreshTime - now);
489
+ this.refreshTimeout = setTimeout(async () => {
490
+ try {
491
+ await this.refreshTokens();
492
+ } catch (error) {
493
+ console.error("Automatic token refresh failed:", error);
494
+ if (this.config.onTokenError) {
495
+ this.config.onTokenError(error);
496
+ }
497
+ }
498
+ }, delay);
499
+ }
500
+ /**
501
+ * Save tokens to storage
502
+ */
503
+ saveTokens() {
504
+ try {
505
+ const { storage } = this;
506
+ const keys = this.storageKeys;
507
+ if (this.tokens.accessToken) {
508
+ storage.setItem(keys.accessToken, this.tokens.accessToken);
509
+ }
510
+ if (this.tokens.refreshToken) {
511
+ storage.setItem(keys.refreshToken, this.tokens.refreshToken);
512
+ }
513
+ if (this.tokens.expiresAt) {
514
+ storage.setItem(keys.expiresAt, this.tokens.expiresAt.toString());
515
+ }
516
+ if (this.tokens.expiresIn) {
517
+ storage.setItem(keys.expiresIn, this.tokens.expiresIn.toString());
518
+ }
519
+ } catch (error) {
520
+ console.error("[TokenManager] Error saving tokens to storage:", error);
521
+ }
522
+ }
523
+ /**
524
+ * Load tokens from storage
525
+ */
526
+ loadTokens() {
527
+ try {
528
+ const { storage } = this;
529
+ const keys = this.storageKeys;
530
+ const accessToken = storage.getItem(keys.accessToken);
531
+ const refreshToken = storage.getItem(keys.refreshToken);
532
+ const expiresAt = storage.getItem(keys.expiresAt);
533
+ const expiresIn = storage.getItem(keys.expiresIn);
534
+ if (accessToken) {
535
+ this.tokens = {
536
+ accessToken,
537
+ refreshToken,
538
+ expiresAt: expiresAt ? parseInt(expiresAt, 10) : null,
539
+ expiresIn: expiresIn ? parseInt(expiresIn, 10) : null,
540
+ tokenType: "Bearer"
541
+ };
542
+ this.scheduleRefresh();
543
+ }
544
+ } catch (error) {
545
+ console.error("[TokenManager] Error loading tokens from storage:", error);
546
+ this.tokens = {
547
+ accessToken: null,
548
+ refreshToken: null,
549
+ expiresAt: null,
550
+ expiresIn: null
551
+ };
552
+ }
553
+ }
554
+ /**
555
+ * Clear all tokens
556
+ */
557
+ clearTokens() {
558
+ this.tokens = {
559
+ accessToken: null,
560
+ refreshToken: null,
561
+ expiresAt: null,
562
+ expiresIn: null
563
+ };
564
+ const { storage } = this;
565
+ const keys = this.storageKeys;
566
+ Object.values(keys).forEach((key) => {
567
+ storage.removeItem(key);
568
+ });
569
+ if (this.refreshTimeout) {
570
+ clearTimeout(this.refreshTimeout);
571
+ this.refreshTimeout = null;
572
+ }
573
+ this.retryCount = 0;
574
+ }
575
+ /**
576
+ * Get token status information
577
+ */
578
+ getTokenStatus() {
579
+ const hasTokens = this.hasTokens();
580
+ const isValid = this.isAccessTokenValid();
581
+ const { expiresAt } = this.tokens;
582
+ const timeToExpiry = expiresAt ? expiresAt - Date.now() : null;
583
+ return {
584
+ hasTokens,
585
+ isValid,
586
+ hasRefreshToken: this.hasRefreshToken(),
587
+ expiresAt,
588
+ timeToExpiry,
589
+ willExpireSoon: timeToExpiry ? timeToExpiry < this.config.refreshBuffer : false
590
+ };
591
+ }
592
+ /**
593
+ * Cleanup resources
594
+ */
595
+ destroy() {
596
+ if (this.refreshTimeout) {
597
+ clearTimeout(this.refreshTimeout);
598
+ this.refreshTimeout = null;
599
+ }
600
+ this.refreshPromise = null;
601
+ }
602
+ };
603
+ var defaultTokenManager = null;
604
+ var getTokenManager = (options) => {
605
+ if (!defaultTokenManager) {
606
+ defaultTokenManager = new TokenManager(options);
607
+ }
608
+ return defaultTokenManager;
609
+ };
610
+
611
+ // src/services/BaseService.js
612
+ var BaseService = class {
613
+ constructor({ context, options } = {}) {
614
+ this._context = context || {};
615
+ this._options = options || {};
616
+ this._ready = false;
617
+ this._error = null;
618
+ this._apiUrl = null;
619
+ this._tokenManager = null;
620
+ }
621
+ // Initialize service
622
+ init({ context }) {
623
+ try {
624
+ const { apiUrl } = context || this._context;
625
+ this._apiUrl = apiUrl || environment_default.apiUrl;
626
+ if (!this._apiUrl) {
627
+ throw new Error("Service base URL not configured");
628
+ }
629
+ this._tokenManager = getTokenManager({
630
+ apiUrl: this._apiUrl,
631
+ onTokenError: (error) => {
632
+ console.error("Token management error:", error);
633
+ }
634
+ });
635
+ this._setReady();
636
+ } catch (error) {
637
+ this._setError(error);
638
+ throw error;
639
+ }
640
+ }
641
+ // Update context
642
+ updateContext(context) {
643
+ if (context && typeof context === "object") {
644
+ Object.assign(this._context, context);
645
+ }
646
+ }
647
+ // Get service status
648
+ getStatus() {
649
+ return {
650
+ ready: this._ready,
651
+ error: this._error,
652
+ context: { ...this._context }
653
+ };
654
+ }
655
+ // Check if service is ready
656
+ isReady() {
657
+ return this._ready;
658
+ }
659
+ // Protected helper methods
660
+ _setReady(ready = true) {
661
+ this._ready = ready;
662
+ this._error = null;
663
+ }
664
+ _setError(error) {
665
+ this._ready = false;
666
+ this._error = error;
667
+ }
668
+ _getTrackingService() {
669
+ var _a;
670
+ const services = (_a = this._context) == null ? void 0 : _a.services;
671
+ const tracking = services == null ? void 0 : services.tracking;
672
+ if (!tracking || typeof tracking.trackError !== "function") {
673
+ return null;
674
+ }
675
+ return tracking;
676
+ }
677
+ _shouldTrackErrors() {
678
+ var _a;
679
+ const name = (_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name;
680
+ return name !== "TrackingService";
681
+ }
682
+ _trackServiceError(error, details = {}) {
683
+ var _a;
684
+ if (!this._shouldTrackErrors()) {
685
+ return;
686
+ }
687
+ try {
688
+ const tracking = this._getTrackingService();
689
+ if (!tracking) {
690
+ return;
691
+ }
692
+ const context = {
693
+ service: ((_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name) || "UnknownService",
694
+ apiUrl: this._apiUrl || null,
695
+ ...details
696
+ };
697
+ tracking.trackError(error instanceof Error ? error : new Error(String(error)), context);
698
+ } catch {
699
+ }
700
+ }
701
+ _requireAuth() {
702
+ if (!this.getAuthToken()) {
703
+ throw new Error("Authentication required");
704
+ }
705
+ }
706
+ _requireReady(methodName = "unknown") {
707
+ if (!this.isReady()) {
708
+ throw new Error(`Service not initialized for method: ${methodName}`);
709
+ }
710
+ }
711
+ // Shared HTTP request method
712
+ async _request(endpoint, options = {}) {
713
+ const url = `${this._apiUrl}/core${endpoint}`;
714
+ const defaultHeaders = {};
715
+ if (!(options.body instanceof FormData)) {
716
+ defaultHeaders["Content-Type"] = "application/json";
717
+ }
718
+ if (this._requiresInit(options.methodName) && this._tokenManager) {
719
+ try {
720
+ const validToken = await this._tokenManager.ensureValidToken();
721
+ if (validToken) {
722
+ const authHeader = this._tokenManager.getAuthHeader();
723
+ if (authHeader) {
724
+ defaultHeaders.Authorization = authHeader;
725
+ }
726
+ }
727
+ } catch (error) {
728
+ console.warn(
729
+ "Token management failed, proceeding without authentication:",
730
+ error
731
+ );
732
+ }
733
+ }
734
+ try {
735
+ const response = await fetch(url, {
736
+ ...options,
737
+ headers: {
738
+ ...defaultHeaders,
739
+ ...options.headers
740
+ }
741
+ });
742
+ if (!response.ok) {
743
+ let error = {
744
+ message: `HTTP ${response.status}: ${response.statusText}`
745
+ };
746
+ try {
747
+ error = await response.json();
748
+ } catch {
749
+ }
750
+ this._trackServiceError(
751
+ new Error(error.message || error.error || `HTTP ${response.status}: ${response.statusText}`),
752
+ {
753
+ endpoint,
754
+ methodName: options.methodName,
755
+ status: response.status,
756
+ statusText: response.statusText
757
+ }
758
+ );
759
+ throw new Error(error.message || error.error || "Request failed", { cause: error });
760
+ }
761
+ return response.status === 204 ? null : response.json();
762
+ } catch (error) {
763
+ this._trackServiceError(error, {
764
+ endpoint,
765
+ methodName: options.methodName
766
+ });
767
+ throw new Error(`Request failed: ${error.message}`, { cause: error });
768
+ }
769
+ }
770
+ // Helper method to determine if a method requires initialization
771
+ _requiresInit(methodName) {
772
+ const noInitMethods = /* @__PURE__ */ new Set([
773
+ "register",
774
+ "login",
775
+ "googleAuth",
776
+ "googleAuthCallback",
777
+ "githubAuth",
778
+ "requestPasswordReset",
779
+ "confirmPasswordReset",
780
+ "confirmRegistration",
781
+ "verifyEmail",
782
+ "getPlans",
783
+ "getPlan",
784
+ "listPublicProjects",
785
+ "getPublicProject"
786
+ ]);
787
+ return !noInitMethods.has(methodName);
788
+ }
789
+ // Cleanup method
790
+ destroy() {
791
+ if (this._tokenManager) {
792
+ this._tokenManager.destroy();
793
+ this._tokenManager = null;
794
+ }
795
+ this._ready = false;
796
+ this._setReady(false);
797
+ }
798
+ };
799
+
800
+ // src/utils/ordering.js
801
+ function isObjectLike(val) {
802
+ return val && typeof val === "object" && !Array.isArray(val);
803
+ }
804
+ function normalizePath(path) {
805
+ if (Array.isArray(path)) {
806
+ return path;
807
+ }
808
+ if (typeof path === "string") {
809
+ return [path];
810
+ }
811
+ return [];
812
+ }
813
+ function getParentPathsFromTuples(tuples = []) {
814
+ const seen = /* @__PURE__ */ new Set();
815
+ const parents = [];
816
+ const META_KEYS = /* @__PURE__ */ new Set([
817
+ "style",
818
+ "class",
819
+ "text",
820
+ "html",
821
+ "content",
822
+ "data",
823
+ "attr",
824
+ "state",
825
+ "scope",
826
+ "define",
827
+ "on",
828
+ "extend",
829
+ "extends",
830
+ "childExtend",
831
+ "childExtends",
832
+ "children",
833
+ "component",
834
+ "context",
835
+ "tag",
836
+ "key",
837
+ "__order",
838
+ "if"
839
+ ]);
840
+ for (let i = 0; i < tuples.length; i++) {
841
+ const tuple = tuples[i];
842
+ if (!Array.isArray(tuple) || tuple.length < 2) {
843
+ continue;
844
+ }
845
+ const path = normalizePath(tuple[1]);
846
+ if (!path.length) {
847
+ continue;
848
+ }
849
+ if (path[0] === "schema") {
850
+ continue;
851
+ }
852
+ const immediateParent = path.slice(0, -1);
853
+ if (immediateParent.length) {
854
+ const key = JSON.stringify(immediateParent);
855
+ if (!seen.has(key)) {
856
+ seen.add(key);
857
+ parents.push(immediateParent);
858
+ }
859
+ }
860
+ const last = path[path.length - 1];
861
+ if (META_KEYS.has(last) && path.length >= 2) {
862
+ const containerParent = path.slice(0, -2);
863
+ if (containerParent.length) {
864
+ const key2 = JSON.stringify(containerParent);
865
+ if (!seen.has(key2)) {
866
+ seen.add(key2);
867
+ parents.push(containerParent);
868
+ }
869
+ }
870
+ }
871
+ for (let j = 0; j < path.length; j++) {
872
+ const seg = path[j];
873
+ if (!META_KEYS.has(seg)) {
874
+ continue;
875
+ }
876
+ const containerParent2 = path.slice(0, j);
877
+ if (!containerParent2.length) {
878
+ continue;
879
+ }
880
+ const key3 = JSON.stringify(containerParent2);
881
+ if (!seen.has(key3)) {
882
+ seen.add(key3);
883
+ parents.push(containerParent2);
884
+ }
885
+ }
886
+ }
887
+ return parents;
888
+ }
889
+ function computeOrdersFromState(root, parentPaths = []) {
890
+ if (!root || typeof root.getByPath !== "function") {
891
+ return [];
892
+ }
893
+ const orders = [];
894
+ const EXCLUDE_KEYS = /* @__PURE__ */ new Set(["__order"]);
895
+ for (let i = 0; i < parentPaths.length; i++) {
896
+ const parentPath = parentPaths[i];
897
+ const obj = (() => {
898
+ try {
899
+ return root.getByPath(parentPath);
900
+ } catch {
901
+ return null;
902
+ }
903
+ })();
904
+ if (!isObjectLike(obj)) {
905
+ continue;
906
+ }
907
+ const keys = Object.keys(obj).filter((k) => !EXCLUDE_KEYS.has(k));
908
+ orders.push({ path: parentPath, keys });
909
+ }
910
+ return orders;
911
+ }
912
+ function normaliseSchemaCode(code) {
913
+ if (typeof code !== "string" || !code.length) {
914
+ return "";
915
+ }
916
+ return code.replaceAll("/////n", "\n").replaceAll("/////tilde", "`");
917
+ }
918
+ function parseExportedObject(code) {
919
+ const src = normaliseSchemaCode(code);
920
+ if (!src) {
921
+ return null;
922
+ }
923
+ const body = src.replace(/^\s*export\s+default\s*/u, "return ");
924
+ try {
925
+ return new Function(body)();
926
+ } catch {
927
+ return null;
928
+ }
929
+ }
930
+ function extractTopLevelKeysFromCode(code) {
931
+ const obj = parseExportedObject(code);
932
+ if (!obj || typeof obj !== "object") {
933
+ return [];
934
+ }
935
+ return Object.keys(obj);
936
+ }
937
+ function computeOrdersForTuples(root, tuples = []) {
938
+ const pendingChildrenByContainer = /* @__PURE__ */ new Map();
939
+ for (let i = 0; i < tuples.length; i++) {
940
+ const t = tuples[i];
941
+ if (!Array.isArray(t)) {
942
+ continue;
943
+ }
944
+ const [action, path] = t;
945
+ const p = normalizePath(path);
946
+ if (!Array.isArray(p) || p.length < 2) {
947
+ continue;
948
+ }
949
+ if (p[0] === "schema") {
950
+ continue;
951
+ }
952
+ const containerPath = p.slice(0, -1);
953
+ const childKey = p[p.length - 1];
954
+ const key = JSON.stringify(containerPath);
955
+ if (!pendingChildrenByContainer.has(key)) {
956
+ pendingChildrenByContainer.set(key, /* @__PURE__ */ new Set());
957
+ }
958
+ if (action === "update" || action === "set") {
959
+ pendingChildrenByContainer.get(key).add(childKey);
960
+ }
961
+ }
962
+ const preferredOrderMap = /* @__PURE__ */ new Map();
963
+ for (let i = 0; i < tuples.length; i++) {
964
+ const t = tuples[i];
965
+ if (!Array.isArray(t)) {
966
+ continue;
967
+ }
968
+ const [action, path, value] = t;
969
+ const p = normalizePath(path);
970
+ if (action !== "update" || !Array.isArray(p) || p.length < 3) {
971
+ continue;
972
+ }
973
+ if (p[0] !== "schema") {
974
+ continue;
975
+ }
976
+ const [, type, key] = p;
977
+ const containerPath = [type, key];
978
+ const uses = value && Array.isArray(value.uses) ? value.uses : null;
979
+ const code = value && value.code;
980
+ const obj = (() => {
981
+ try {
982
+ return root && typeof root.getByPath === "function" ? root.getByPath(containerPath) : null;
983
+ } catch {
984
+ return null;
985
+ }
986
+ })();
987
+ if (!obj) {
988
+ continue;
989
+ }
990
+ const present = new Set(Object.keys(obj));
991
+ const EXCLUDE_KEYS = /* @__PURE__ */ new Set(["__order"]);
992
+ const codeKeys = extractTopLevelKeysFromCode(code);
993
+ let resolved = [];
994
+ const pendingKey = JSON.stringify(containerPath);
995
+ const pendingChildren = pendingChildrenByContainer.get(pendingKey) || /* @__PURE__ */ new Set();
996
+ const eligible = /* @__PURE__ */ new Set([...present, ...pendingChildren]);
997
+ if (Array.isArray(codeKeys) && codeKeys.length) {
998
+ resolved = codeKeys.filter((k) => eligible.has(k) && !EXCLUDE_KEYS.has(k));
999
+ }
1000
+ if (Array.isArray(uses) && uses.length) {
1001
+ for (let u = 0; u < uses.length; u++) {
1002
+ const keyName = uses[u];
1003
+ if (eligible.has(keyName) && !EXCLUDE_KEYS.has(keyName) && !resolved.includes(keyName)) {
1004
+ resolved.push(keyName);
1005
+ }
1006
+ }
1007
+ }
1008
+ if (pendingChildren.size) {
1009
+ for (const child of pendingChildren) {
1010
+ if (!EXCLUDE_KEYS.has(child) && !resolved.includes(child)) {
1011
+ resolved.push(child);
1012
+ }
1013
+ }
1014
+ }
1015
+ if (resolved.length) {
1016
+ preferredOrderMap.set(JSON.stringify(containerPath), { path: containerPath, keys: resolved });
1017
+ }
1018
+ }
1019
+ const parents = getParentPathsFromTuples(tuples);
1020
+ const orders = [];
1021
+ const seen = /* @__PURE__ */ new Set();
1022
+ preferredOrderMap.forEach((v) => {
1023
+ const k = JSON.stringify(v.path);
1024
+ if (!seen.has(k)) {
1025
+ seen.add(k);
1026
+ orders.push(v);
1027
+ }
1028
+ });
1029
+ const fallbackOrders = computeOrdersFromState(root, parents);
1030
+ for (let i = 0; i < fallbackOrders.length; i++) {
1031
+ const v = fallbackOrders[i];
1032
+ const k = JSON.stringify(v.path);
1033
+ if (seen.has(k)) {
1034
+ continue;
1035
+ }
1036
+ const pending = pendingChildrenByContainer.get(k);
1037
+ if (pending && pending.size) {
1038
+ const existingKeys = v.keys;
1039
+ const existingSet = new Set(existingKeys);
1040
+ const META_KEYS = /* @__PURE__ */ new Set([
1041
+ "style",
1042
+ "class",
1043
+ "text",
1044
+ "html",
1045
+ "content",
1046
+ "data",
1047
+ "attr",
1048
+ "state",
1049
+ "scope",
1050
+ "define",
1051
+ "on",
1052
+ "extend",
1053
+ "extends",
1054
+ "childExtend",
1055
+ "childExtends",
1056
+ "children",
1057
+ "component",
1058
+ "context",
1059
+ "tag",
1060
+ "key",
1061
+ "__order",
1062
+ "if"
1063
+ ]);
1064
+ let firstMetaIndex = existingKeys.length;
1065
+ for (let j = 0; j < existingKeys.length; j++) {
1066
+ if (META_KEYS.has(existingKeys[j])) {
1067
+ firstMetaIndex = j;
1068
+ break;
1069
+ }
1070
+ }
1071
+ for (const child of pending) {
1072
+ if (existingSet.has(child)) {
1073
+ continue;
1074
+ }
1075
+ const insertIndex = firstMetaIndex;
1076
+ existingKeys.splice(insertIndex, 0, child);
1077
+ existingSet.add(child);
1078
+ firstMetaIndex++;
1079
+ }
1080
+ }
1081
+ seen.add(k);
1082
+ orders.push(v);
1083
+ }
1084
+ return orders;
1085
+ }
1086
+
1087
+ // src/utils/jsonDiff.js
1088
+ function isPlainObject(o) {
1089
+ return o && typeof o === "object" && !Array.isArray(o);
1090
+ }
1091
+ function deepEqual(a, b) {
1092
+ if (Object.is(a, b)) {
1093
+ return true;
1094
+ }
1095
+ if (typeof a === "function" && typeof b === "function") {
1096
+ try {
1097
+ return a.toString() === b.toString();
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ }
1102
+ if (typeof a === "function" || typeof b === "function") {
1103
+ return false;
1104
+ }
1105
+ if (a instanceof Date && b instanceof Date) {
1106
+ return a.getTime() === b.getTime();
1107
+ }
1108
+ if (a instanceof RegExp && b instanceof RegExp) {
1109
+ return String(a) === String(b);
1110
+ }
1111
+ if (Array.isArray(a) && Array.isArray(b)) {
1112
+ if (a.length !== b.length) {
1113
+ return false;
1114
+ }
1115
+ for (let i = 0; i < a.length; i++) {
1116
+ if (!deepEqual(a[i], b[i])) {
1117
+ return false;
1118
+ }
1119
+ }
1120
+ return true;
1121
+ }
1122
+ if (a && b && typeof a === "object" && typeof b === "object") {
1123
+ const aKeys = Object.keys(a);
1124
+ const bKeys = Object.keys(b);
1125
+ if (aKeys.length !== bKeys.length) {
1126
+ return false;
1127
+ }
1128
+ for (let i = 0; i < aKeys.length; i++) {
1129
+ const key = aKeys[i];
1130
+ if (!Object.hasOwn(b, key)) {
1131
+ return false;
1132
+ }
1133
+ if (!deepEqual(a[key], b[key])) {
1134
+ return false;
1135
+ }
1136
+ }
1137
+ return true;
1138
+ }
1139
+ return false;
1140
+ }
1141
+ function diffJson(prev, next, prefix = []) {
1142
+ const ops = [];
1143
+ const _prefix = Array.isArray(prefix) ? prefix : [];
1144
+ for (const key in prev) {
1145
+ if (Object.hasOwn(prev, key) && !(key in next)) {
1146
+ ops.push({ action: "del", path: [..._prefix, key] });
1147
+ }
1148
+ }
1149
+ for (const key in next) {
1150
+ if (Object.hasOwn(next, key)) {
1151
+ const pVal = prev == null ? void 0 : prev[key];
1152
+ const nVal = next[key];
1153
+ if (isPlainObject(pVal) && isPlainObject(nVal)) {
1154
+ ops.push(...diffJson(pVal, nVal, [..._prefix, key]));
1155
+ } else if (!deepEqual(pVal, nVal)) {
1156
+ ops.push({ action: "set", path: [..._prefix, key], value: nVal });
1157
+ }
1158
+ }
1159
+ }
1160
+ return ops;
1161
+ }
1162
+
1163
+ // src/utils/changePreprocessor.js
1164
+ function isPlainObject2(val) {
1165
+ return val && typeof val === "object" && !Array.isArray(val);
1166
+ }
1167
+ function getByPathSafe(root, path) {
1168
+ if (!root || typeof root.getByPath !== "function") {
1169
+ return null;
1170
+ }
1171
+ try {
1172
+ return root.getByPath(path);
1173
+ } catch {
1174
+ return null;
1175
+ }
1176
+ }
1177
+ function resolveNextValueFromTuples(tuples, path) {
1178
+ if (!Array.isArray(tuples) || !Array.isArray(path)) {
1179
+ return null;
1180
+ }
1181
+ for (let i = tuples.length - 1; i >= 0; i--) {
1182
+ const t = tuples[i];
1183
+ if (!Array.isArray(t) || t.length < 3) {
1184
+ continue;
1185
+ }
1186
+ const [action, tuplePath, tupleValue] = t;
1187
+ if (action !== "update" && action !== "set" || !Array.isArray(tuplePath)) {
1188
+ continue;
1189
+ }
1190
+ if (tuplePath.length > path.length) {
1191
+ continue;
1192
+ }
1193
+ let isPrefix = true;
1194
+ for (let j = 0; j < tuplePath.length; j++) {
1195
+ if (tuplePath[j] !== path[j]) {
1196
+ isPrefix = false;
1197
+ break;
1198
+ }
1199
+ }
1200
+ if (!isPrefix) {
1201
+ continue;
1202
+ }
1203
+ if (tuplePath.length === path.length) {
1204
+ return tupleValue;
1205
+ }
1206
+ let current = tupleValue;
1207
+ for (let j = tuplePath.length; j < path.length; j++) {
1208
+ if (current == null) {
1209
+ return null;
1210
+ }
1211
+ current = current[path[j]];
1212
+ }
1213
+ if (current !== null) {
1214
+ return current;
1215
+ }
1216
+ }
1217
+ return null;
1218
+ }
1219
+ function preprocessChanges(root, tuples = [], options = {}) {
1220
+ const expandTuple = (t) => {
1221
+ const [action, path, value] = t || [];
1222
+ const isSchemaPath = Array.isArray(path) && path[0] === "schema";
1223
+ const isFilesPath = Array.isArray(path) && path[0] === "files";
1224
+ if (action === "delete") {
1225
+ return [t];
1226
+ }
1227
+ const canConsiderExpansion = action === "update" && Array.isArray(path) && (path.length === 1 || path.length === 2 || isSchemaPath && path.length === 3) && isPlainObject2(value);
1228
+ if (!canConsiderExpansion || isFilesPath || value && value.type === "files") {
1229
+ return [t];
1230
+ }
1231
+ const prevRaw = getByPathSafe(root, path);
1232
+ const isCreatePath = Array.isArray(path) && action === "update" && // e.g. ['update', ['components', 'NewKey'], {...}]
1233
+ (!isSchemaPath && path.length === 2 || // e.g. ['update', ['schema', 'components', 'NewKey'], {...}]
1234
+ isSchemaPath && path.length === 3) && (prevRaw === null || typeof prevRaw === "undefined");
1235
+ if (isCreatePath) {
1236
+ return [t];
1237
+ }
1238
+ const prev = prevRaw || {};
1239
+ const next = value || {};
1240
+ if (!isPlainObject2(prev) || !isPlainObject2(next)) {
1241
+ return [t];
1242
+ }
1243
+ const ops = diffJson(prev, next, []);
1244
+ if (!ops.length) {
1245
+ return [t];
1246
+ }
1247
+ const out = [];
1248
+ for (let i = 0; i < ops.length; i++) {
1249
+ const op = ops[i];
1250
+ const fullPath = [...path, ...op.path];
1251
+ const last = fullPath[fullPath.length - 1];
1252
+ if (op.action === "set") {
1253
+ out.push(["update", fullPath, op.value]);
1254
+ } else if (op.action === "del") {
1255
+ if (last !== "__order") {
1256
+ out.push(["delete", fullPath]);
1257
+ }
1258
+ }
1259
+ }
1260
+ return out;
1261
+ };
1262
+ const minimizeTuples = (input) => {
1263
+ const out = [];
1264
+ const seen2 = /* @__PURE__ */ new Set();
1265
+ for (let i = 0; i < input.length; i++) {
1266
+ const expanded = expandTuple(input[i]);
1267
+ for (let k = 0; k < expanded.length; k++) {
1268
+ const tuple = expanded[k];
1269
+ const isDelete = Array.isArray(tuple) && tuple[0] === "delete";
1270
+ const isOrderKey = isDelete && Array.isArray(tuple[1]) && tuple[1][tuple[1].length - 1] === "__order";
1271
+ if (!isOrderKey) {
1272
+ const key = JSON.stringify(tuple);
1273
+ if (!seen2.has(key)) {
1274
+ seen2.add(key);
1275
+ out.push(tuple);
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+ return out;
1281
+ };
1282
+ const granularChanges = (() => {
1283
+ try {
1284
+ const res = minimizeTuples(tuples);
1285
+ if (options.append && options.append.length) {
1286
+ res.push(...options.append);
1287
+ }
1288
+ return res;
1289
+ } catch {
1290
+ return Array.isArray(tuples) ? tuples.slice() : [];
1291
+ }
1292
+ })();
1293
+ const hydratedGranularChanges = granularChanges.map((t) => {
1294
+ if (!Array.isArray(t) || t.length < 3) {
1295
+ return t;
1296
+ }
1297
+ const [action, path] = t;
1298
+ if (action !== "update" && action !== "set" || !Array.isArray(path)) {
1299
+ return t;
1300
+ }
1301
+ const nextValue = resolveNextValueFromTuples(tuples, path);
1302
+ if (nextValue === null) {
1303
+ return t;
1304
+ }
1305
+ return [action, path, nextValue];
1306
+ });
1307
+ const baseOrders = computeOrdersForTuples(root, hydratedGranularChanges);
1308
+ const preferOrdersMap = /* @__PURE__ */ new Map();
1309
+ for (let i = 0; i < tuples.length; i++) {
1310
+ const t = tuples[i];
1311
+ if (!Array.isArray(t) || t.length < 3) {
1312
+ continue;
1313
+ }
1314
+ const [action, path, value] = t;
1315
+ const isFilesPath = Array.isArray(path) && path[0] === "files";
1316
+ if (action !== "update" || !Array.isArray(path) || path.length !== 1 && path.length !== 2 || !isPlainObject2(value) || isFilesPath || value && value.type === "files") {
1317
+ continue;
1318
+ }
1319
+ const keys = Object.keys(value).filter((k) => k !== "__order");
1320
+ const key = JSON.stringify(path);
1321
+ preferOrdersMap.set(key, { path, keys });
1322
+ }
1323
+ const mergedOrders = [];
1324
+ const seen = /* @__PURE__ */ new Set();
1325
+ preferOrdersMap.forEach((v, k) => {
1326
+ seen.add(k);
1327
+ mergedOrders.push(v);
1328
+ });
1329
+ for (let i = 0; i < baseOrders.length; i++) {
1330
+ const v = baseOrders[i];
1331
+ const k = JSON.stringify(v.path);
1332
+ if (!seen.has(k)) {
1333
+ seen.add(k);
1334
+ mergedOrders.push(v);
1335
+ }
1336
+ }
1337
+ return { granularChanges: hydratedGranularChanges, orders: mergedOrders };
1338
+ }
1339
+
1340
+ // src/services/ProjectService.js
1341
+ import { deepStringifyFunctions } from "@domql/utils";
1342
+ var ProjectService = class extends BaseService {
1343
+ // ==================== PROJECT METHODS ====================
1344
+ async createProject(projectData) {
1345
+ this._requireReady("createProject");
1346
+ try {
1347
+ const response = await this._request("/projects", {
1348
+ method: "POST",
1349
+ body: JSON.stringify(projectData),
1350
+ methodName: "createProject"
1351
+ });
1352
+ if (response.success) {
1353
+ return response.data;
1354
+ }
1355
+ throw new Error(response.message);
1356
+ } catch (error) {
1357
+ throw new Error(`Failed to create project: ${error.message}`, { cause: error });
1358
+ }
1359
+ }
1360
+ async getProjects(params = {}) {
1361
+ this._requireReady("getProjects");
1362
+ try {
1363
+ const queryParams = new URLSearchParams();
1364
+ Object.keys(params).forEach((key) => {
1365
+ if (params[key] != null) {
1366
+ queryParams.append(key, params[key]);
1367
+ }
1368
+ });
1369
+ const queryString = queryParams.toString();
1370
+ const url = `/projects${queryString ? `?${queryString}` : ""}`;
1371
+ const response = await this._request(url, {
1372
+ method: "GET",
1373
+ methodName: "getProjects"
1374
+ });
1375
+ if (response.success) {
1376
+ return response;
1377
+ }
1378
+ throw new Error(response.message);
1379
+ } catch (error) {
1380
+ throw new Error(`Failed to get projects: ${error.message}`, { cause: error });
1381
+ }
1382
+ }
1383
+ /**
1384
+ * Alias for getProjects for consistency with API naming
1385
+ */
1386
+ async listProjects(params = {}) {
1387
+ return await this.getProjects(params);
1388
+ }
1389
+ /**
1390
+ * List only public projects (no authentication required)
1391
+ */
1392
+ async listPublicProjects(params = {}) {
1393
+ try {
1394
+ const queryParams = new URLSearchParams();
1395
+ Object.keys(params).forEach((key) => {
1396
+ if (params[key] != null) {
1397
+ queryParams.append(key, params[key]);
1398
+ }
1399
+ });
1400
+ const queryString = queryParams.toString();
1401
+ const url = `/projects/public${queryString ? `?${queryString}` : ""}`;
1402
+ const response = await this._request(url, {
1403
+ method: "GET",
1404
+ methodName: "listPublicProjects"
1405
+ });
1406
+ if (response.success) {
1407
+ return response.data;
1408
+ }
1409
+ throw new Error(response.message);
1410
+ } catch (error) {
1411
+ throw new Error(`Failed to list public projects: ${error.message}`, { cause: error });
1412
+ }
1413
+ }
1414
+ async getProject(projectId) {
1415
+ this._requireReady("getProject");
1416
+ if (!projectId) {
1417
+ throw new Error("Project ID is required");
1418
+ }
1419
+ try {
1420
+ const response = await this._request(`/projects/${projectId}`, {
1421
+ method: "GET",
1422
+ methodName: "getProject"
1423
+ });
1424
+ if (response.success) {
1425
+ const iconSrc = response.data.icon ? `${this._apiUrl}/core/files/public/${response.data.icon.id}/download` : null;
1426
+ return {
1427
+ ...response.data,
1428
+ icon: { src: iconSrc, ...response.data.icon }
1429
+ };
1430
+ }
1431
+ throw new Error(response.message);
1432
+ } catch (error) {
1433
+ throw new Error(`Failed to get project: ${error.message}`, { cause: error });
1434
+ }
1435
+ }
1436
+ /**
1437
+ * Get a public project by ID (no authentication required)
1438
+ * Corresponds to router.get('/public/:projectId', ProjectController.getPublicProject)
1439
+ */
1440
+ async getPublicProject(projectId) {
1441
+ if (!projectId) {
1442
+ throw new Error("Project ID is required");
1443
+ }
1444
+ try {
1445
+ const response = await this._request(`/projects/public/${projectId}`, {
1446
+ method: "GET",
1447
+ methodName: "getPublicProject"
1448
+ });
1449
+ if (response.success) {
1450
+ const iconSrc = response.data.icon ? `${this._apiUrl}/core/files/public/${response.data.icon.id}/download` : null;
1451
+ return {
1452
+ ...response.data,
1453
+ icon: { src: iconSrc, ...response.data.icon }
1454
+ };
1455
+ }
1456
+ throw new Error(response.message);
1457
+ } catch (error) {
1458
+ throw new Error(`Failed to get public project: ${error.message}`, { cause: error });
1459
+ }
1460
+ }
1461
+ async getProjectByKey(key) {
1462
+ this._requireReady("getProjectByKey");
1463
+ if (!key) {
1464
+ throw new Error("Project key is required");
1465
+ }
1466
+ try {
1467
+ const response = await this._request(`/projects/key/${key}`, {
1468
+ method: "GET",
1469
+ methodName: "getProjectByKey"
1470
+ });
1471
+ if (response.success) {
1472
+ const iconSrc = response.data.icon ? `${this._apiUrl}/core/files/public/${response.data.icon.id}/download` : null;
1473
+ return {
1474
+ ...response.data,
1475
+ icon: { src: iconSrc, ...response.data.icon }
1476
+ };
1477
+ }
1478
+ throw new Error(response.message);
1479
+ } catch (error) {
1480
+ throw new Error(`Failed to get project by key: ${error.message}`, { cause: error });
1481
+ }
1482
+ }
1483
+ /**
1484
+ * Get current project data by key (no project ID required)
1485
+ */
1486
+ async getProjectDataByKey(key, options = {}) {
1487
+ this._requireReady("getProjectDataByKey");
1488
+ if (!key) {
1489
+ throw new Error("Project key is required");
1490
+ }
1491
+ const {
1492
+ branch = "main",
1493
+ version = "latest",
1494
+ includeHistory = false,
1495
+ headers
1496
+ } = options;
1497
+ const queryParams = new URLSearchParams({
1498
+ branch,
1499
+ version,
1500
+ includeHistory: includeHistory.toString()
1501
+ }).toString();
1502
+ try {
1503
+ const response = await this._request(
1504
+ `/projects/key/${key}/data?${queryParams}`,
1505
+ {
1506
+ method: "GET",
1507
+ methodName: "getProjectDataByKey",
1508
+ ...headers ? { headers } : {}
1509
+ }
1510
+ );
1511
+ if (response.success) {
1512
+ return response.data;
1513
+ }
1514
+ throw new Error(response.message);
1515
+ } catch (error) {
1516
+ throw new Error(`Failed to get project data by key: ${error.message}`, { cause: error });
1517
+ }
1518
+ }
1519
+ async updateProject(projectId, data) {
1520
+ this._requireReady("updateProject");
1521
+ if (!projectId) {
1522
+ throw new Error("Project ID is required");
1523
+ }
1524
+ try {
1525
+ const response = await this._request(`/projects/${projectId}`, {
1526
+ method: "PATCH",
1527
+ body: JSON.stringify(data),
1528
+ methodName: "updateProject"
1529
+ });
1530
+ if (response.success) {
1531
+ return response.data;
1532
+ }
1533
+ throw new Error(response.message);
1534
+ } catch (error) {
1535
+ throw new Error(`Failed to update project: ${error.message}`, { cause: error });
1536
+ }
1537
+ }
1538
+ async updateProjectComponents(projectId, components) {
1539
+ this._requireReady("updateProjectComponents");
1540
+ if (!projectId) {
1541
+ throw new Error("Project ID is required");
1542
+ }
1543
+ try {
1544
+ const response = await this._request(
1545
+ `/projects/${projectId}/components`,
1546
+ {
1547
+ method: "PATCH",
1548
+ body: JSON.stringify({ components }),
1549
+ methodName: "updateProjectComponents"
1550
+ }
1551
+ );
1552
+ if (response.success) {
1553
+ return response.data;
1554
+ }
1555
+ throw new Error(response.message);
1556
+ } catch (error) {
1557
+ throw new Error(`Failed to update project components: ${error.message}`, { cause: error });
1558
+ }
1559
+ }
1560
+ async updateProjectSettings(projectId, settings) {
1561
+ this._requireReady("updateProjectSettings");
1562
+ if (!projectId) {
1563
+ throw new Error("Project ID is required");
1564
+ }
1565
+ try {
1566
+ const response = await this._request(`/projects/${projectId}/settings`, {
1567
+ method: "PATCH",
1568
+ body: JSON.stringify({ settings }),
1569
+ methodName: "updateProjectSettings"
1570
+ });
1571
+ if (response.success) {
1572
+ return response.data;
1573
+ }
1574
+ throw new Error(response.message);
1575
+ } catch (error) {
1576
+ throw new Error(`Failed to update project settings: ${error.message}`, { cause: error });
1577
+ }
1578
+ }
1579
+ async updateProjectName(projectId, name) {
1580
+ this._requireReady("updateProjectName");
1581
+ if (!projectId) {
1582
+ throw new Error("Project ID is required");
1583
+ }
1584
+ try {
1585
+ const response = await this._request(`/projects/${projectId}`, {
1586
+ method: "PATCH",
1587
+ body: JSON.stringify({ name }),
1588
+ methodName: "updateProjectName"
1589
+ });
1590
+ if (response.success) {
1591
+ return response.data;
1592
+ }
1593
+ throw new Error(response.message);
1594
+ } catch (error) {
1595
+ throw new Error(`Failed to update project name: ${error.message}`, { cause: error });
1596
+ }
1597
+ }
1598
+ async setProjectAccess(projectId, access) {
1599
+ this._requireReady("setProjectAccess");
1600
+ if (!projectId) {
1601
+ throw new Error("Project ID is required");
1602
+ }
1603
+ if (!access) {
1604
+ throw new Error("Access level is required");
1605
+ }
1606
+ const allowedAccessValues = ["account", "team", "organization", "public"];
1607
+ if (!allowedAccessValues.includes(access)) {
1608
+ throw new Error(
1609
+ `Invalid access value: ${access}. Must be one of: ${allowedAccessValues.join(", ")}`
1610
+ );
1611
+ }
1612
+ try {
1613
+ const response = await this._request(`/projects/${projectId}`, {
1614
+ method: "PATCH",
1615
+ body: JSON.stringify({ access }),
1616
+ methodName: "setProjectAccess"
1617
+ });
1618
+ if (response.success) {
1619
+ return response.data;
1620
+ }
1621
+ throw new Error(response.message);
1622
+ } catch (error) {
1623
+ throw new Error(`Failed to set project access: ${error.message}`, { cause: error });
1624
+ }
1625
+ }
1626
+ async setProjectVisibility(projectId, visibility) {
1627
+ this._requireReady("setProjectVisibility");
1628
+ if (!projectId) {
1629
+ throw new Error("Project ID is required");
1630
+ }
1631
+ if (!visibility) {
1632
+ throw new Error("Visibility is required");
1633
+ }
1634
+ const allowedVisibilityValues = ["public", "private", "password-protected"];
1635
+ if (!allowedVisibilityValues.includes(visibility)) {
1636
+ throw new Error(
1637
+ `Invalid visibility value: ${visibility}. Must be one of: ${allowedVisibilityValues.join(", ")}`
1638
+ );
1639
+ }
1640
+ try {
1641
+ const response = await this._request(`/projects/${projectId}`, {
1642
+ method: "PATCH",
1643
+ body: JSON.stringify({ visibility }),
1644
+ methodName: "setProjectVisibility"
1645
+ });
1646
+ if (response.success) {
1647
+ return response.data;
1648
+ }
1649
+ throw new Error(response.message);
1650
+ } catch (error) {
1651
+ throw new Error(`Failed to set project visibility: ${error.message}`, { cause: error });
1652
+ }
1653
+ }
1654
+ async updateProjectPackage(projectId, pkg) {
1655
+ this._requireReady("updateProjectPackage");
1656
+ if (!projectId) {
1657
+ throw new Error("Project ID is required");
1658
+ }
1659
+ try {
1660
+ const response = await this._request(`/projects/${projectId}/package`, {
1661
+ method: "PATCH",
1662
+ body: JSON.stringify({ package: pkg }),
1663
+ methodName: "updateProjectPackage"
1664
+ });
1665
+ if (response.success) {
1666
+ return response.data;
1667
+ }
1668
+ throw new Error(response.message);
1669
+ } catch (error) {
1670
+ throw new Error(`Failed to update project package: ${error.message}`, { cause: error });
1671
+ }
1672
+ }
1673
+ async duplicateProject(projectId, newName, newKey, targetUserId) {
1674
+ this._requireReady("duplicateProject");
1675
+ if (!projectId) {
1676
+ throw new Error("Project ID is required");
1677
+ }
1678
+ try {
1679
+ const response = await this._request(`/projects/${projectId}/duplicate`, {
1680
+ method: "POST",
1681
+ body: JSON.stringify({ name: newName, key: newKey, targetUserId }),
1682
+ methodName: "duplicateProject"
1683
+ });
1684
+ if (response.success) {
1685
+ return response.data;
1686
+ }
1687
+ throw new Error(response.message);
1688
+ } catch (error) {
1689
+ throw new Error(`Failed to duplicate project: ${error.message}`, { cause: error });
1690
+ }
1691
+ }
1692
+ async removeProject(projectId) {
1693
+ this._requireReady("removeProject");
1694
+ if (!projectId) {
1695
+ throw new Error("Project ID is required");
1696
+ }
1697
+ try {
1698
+ const response = await this._request(`/projects/${projectId}`, {
1699
+ method: "DELETE",
1700
+ methodName: "removeProject"
1701
+ });
1702
+ if (response.success) {
1703
+ return response;
1704
+ }
1705
+ throw new Error(response.message);
1706
+ } catch (error) {
1707
+ throw new Error(`Failed to remove project: ${error.message}`, { cause: error });
1708
+ }
1709
+ }
1710
+ async checkProjectKeyAvailability(key) {
1711
+ this._requireReady("checkProjectKeyAvailability");
1712
+ if (!key) {
1713
+ throw new Error("Project key is required");
1714
+ }
1715
+ try {
1716
+ const response = await this._request(`/projects/check-key/${key}`, {
1717
+ method: "GET",
1718
+ methodName: "checkProjectKeyAvailability"
1719
+ });
1720
+ if (response.success) {
1721
+ return response.data;
1722
+ }
1723
+ throw new Error(response.message);
1724
+ } catch (error) {
1725
+ throw new Error(
1726
+ `Failed to check project key availability: ${error.message}`,
1727
+ { cause: error }
1728
+ );
1729
+ }
1730
+ }
1731
+ // ==================== PROJECT PERMISSION CONFIG METHODS ====================
1732
+ /**
1733
+ * Fetch effective role → permissions configuration for a project.
1734
+ * Mirrors ProjectController.getProjectRolePermissionsConfig.
1735
+ */
1736
+ async getProjectRolePermissionsConfig(projectId, options = {}) {
1737
+ this._requireReady("getProjectRolePermissionsConfig");
1738
+ if (!projectId) {
1739
+ throw new Error("Project ID is required");
1740
+ }
1741
+ const { headers } = options;
1742
+ try {
1743
+ const response = await this._request(`/projects/${projectId}/permissions`, {
1744
+ method: "GET",
1745
+ ...headers ? { headers } : {},
1746
+ methodName: "getProjectRolePermissionsConfig"
1747
+ });
1748
+ if (response && response.success) {
1749
+ return response.data;
1750
+ }
1751
+ throw new Error(response.message);
1752
+ } catch (error) {
1753
+ throw new Error(
1754
+ `Failed to get project role permissions config: ${error.message}`,
1755
+ { cause: error }
1756
+ );
1757
+ }
1758
+ }
1759
+ /**
1760
+ * Update project-level role → permissions overrides.
1761
+ * Mirrors ProjectController.updateProjectRolePermissionsConfig.
1762
+ */
1763
+ async updateProjectRolePermissionsConfig(projectId, rolePermissions, options = {}) {
1764
+ this._requireReady("updateProjectRolePermissionsConfig");
1765
+ if (!projectId) {
1766
+ throw new Error("Project ID is required");
1767
+ }
1768
+ if (!rolePermissions || typeof rolePermissions !== "object") {
1769
+ throw new Error("rolePermissions object is required");
1770
+ }
1771
+ const { headers } = options;
1772
+ try {
1773
+ const response = await this._request(`/projects/${projectId}/permissions`, {
1774
+ method: "PATCH",
1775
+ body: JSON.stringify({ rolePermissions }),
1776
+ ...headers ? { headers } : {},
1777
+ methodName: "updateProjectRolePermissionsConfig"
1778
+ });
1779
+ if (response && response.success) {
1780
+ return response.data;
1781
+ }
1782
+ throw new Error(response.message);
1783
+ } catch (error) {
1784
+ throw new Error(
1785
+ `Failed to update project role permissions config: ${error.message}`,
1786
+ { cause: error }
1787
+ );
1788
+ }
1789
+ }
1790
+ // ==================== PROJECT MEMBER METHODS ====================
1791
+ async getProjectMembers(projectId) {
1792
+ this._requireReady("getProjectMembers");
1793
+ if (!projectId) {
1794
+ throw new Error("Project ID is required");
1795
+ }
1796
+ try {
1797
+ const response = await this._request(`/projects/${projectId}/members`, {
1798
+ method: "GET",
1799
+ methodName: "getProjectMembers"
1800
+ });
1801
+ if (response.success) {
1802
+ return response.data;
1803
+ }
1804
+ throw new Error(response.message);
1805
+ } catch (error) {
1806
+ throw new Error(`Failed to get project members: ${error.message}`, { cause: error });
1807
+ }
1808
+ }
1809
+ async inviteMember(projectId, email, role = "guest", options = {}) {
1810
+ this._requireReady("inviteMember");
1811
+ if (!projectId || !email || !role) {
1812
+ throw new Error("Project ID, email, and role are required");
1813
+ }
1814
+ const { name, callbackUrl, headers } = options;
1815
+ const defaultCallbackUrl = typeof window === "undefined" ? "https://app.symbols.com/accept-invite" : `${window.location.origin}/accept-invite`;
1816
+ try {
1817
+ const requestBody = {
1818
+ email,
1819
+ role,
1820
+ callbackUrl: callbackUrl || defaultCallbackUrl
1821
+ };
1822
+ if (name) {
1823
+ requestBody.name = name;
1824
+ }
1825
+ const response = await this._request(`/projects/${projectId}/invite`, {
1826
+ method: "POST",
1827
+ body: JSON.stringify(requestBody),
1828
+ ...headers ? { headers } : {},
1829
+ methodName: "inviteMember"
1830
+ });
1831
+ if (response.success) {
1832
+ return response.data;
1833
+ }
1834
+ throw new Error(response.message);
1835
+ } catch (error) {
1836
+ throw new Error(`Failed to invite member: ${error.message}`, { cause: error });
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Create a magic invite link for a project.
1841
+ * The backend returns a token and URL that can be shared directly.
1842
+ */
1843
+ async createMagicInviteLink(projectId, options = {}) {
1844
+ this._requireReady("createMagicInviteLink");
1845
+ if (!projectId) {
1846
+ throw new Error("Project ID is required");
1847
+ }
1848
+ const { headers } = options;
1849
+ try {
1850
+ const response = await this._request(`/projects/${projectId}/invite-link`, {
1851
+ method: "POST",
1852
+ ...headers ? { headers } : {},
1853
+ methodName: "createMagicInviteLink"
1854
+ });
1855
+ if (response.success) {
1856
+ return response.data;
1857
+ }
1858
+ throw new Error(response.message);
1859
+ } catch (error) {
1860
+ throw new Error(`Failed to create magic invite link: ${error.message}`, { cause: error });
1861
+ }
1862
+ }
1863
+ async acceptInvite(token) {
1864
+ this._requireReady("acceptInvite");
1865
+ if (!token) {
1866
+ throw new Error("Invitation token is required");
1867
+ }
1868
+ try {
1869
+ const response = await this._request("/projects/accept-invite", {
1870
+ method: "POST",
1871
+ body: JSON.stringify({ token }),
1872
+ methodName: "acceptInvite"
1873
+ });
1874
+ if (response.success) {
1875
+ return response.data;
1876
+ }
1877
+ throw new Error(response.message);
1878
+ } catch (error) {
1879
+ throw new Error(`Failed to accept invite: ${error.message}`, { cause: error });
1880
+ }
1881
+ }
1882
+ async updateMemberRole(projectId, memberId, role) {
1883
+ this._requireReady("updateMemberRole");
1884
+ if (!projectId || !memberId || !role) {
1885
+ throw new Error("Project ID, member ID, and role are required");
1886
+ }
1887
+ try {
1888
+ const response = await this._request(
1889
+ `/projects/${projectId}/members/${memberId}`,
1890
+ {
1891
+ method: "PATCH",
1892
+ body: JSON.stringify({ role }),
1893
+ methodName: "updateMemberRole"
1894
+ }
1895
+ );
1896
+ if (response.success) {
1897
+ return response.data;
1898
+ }
1899
+ throw new Error(response.message);
1900
+ } catch (error) {
1901
+ throw new Error(`Failed to update member role: ${error.message}`, { cause: error });
1902
+ }
1903
+ }
1904
+ async removeMember(projectId, memberId) {
1905
+ this._requireReady("removeMember");
1906
+ if (!projectId || !memberId) {
1907
+ throw new Error("Project ID and member ID are required");
1908
+ }
1909
+ try {
1910
+ const response = await this._request(
1911
+ `/projects/${projectId}/members/${memberId}`,
1912
+ {
1913
+ method: "DELETE",
1914
+ methodName: "removeMember"
1915
+ }
1916
+ );
1917
+ if (response.success) {
1918
+ return response.data;
1919
+ }
1920
+ throw new Error(response.message);
1921
+ } catch (error) {
1922
+ throw new Error(`Failed to remove member: ${error.message}`, { cause: error });
1923
+ }
1924
+ }
1925
+ // ==================== PROJECT LIBRARY METHODS ====================
1926
+ async getAvailableLibraries(params = {}) {
1927
+ this._requireReady("getAvailableLibraries");
1928
+ const queryParams = new URLSearchParams(params).toString();
1929
+ try {
1930
+ const response = await this._request(
1931
+ `/projects/libraries/available?${queryParams}`,
1932
+ {
1933
+ method: "GET",
1934
+ methodName: "getAvailableLibraries"
1935
+ }
1936
+ );
1937
+ if (response.success) {
1938
+ return response.data;
1939
+ }
1940
+ throw new Error(response.message);
1941
+ } catch (error) {
1942
+ throw new Error(`Failed to get available libraries: ${error.message}`, { cause: error });
1943
+ }
1944
+ }
1945
+ async getProjectLibraries(projectId) {
1946
+ this._requireReady("getProjectLibraries");
1947
+ if (!projectId) {
1948
+ throw new Error("Project ID is required");
1949
+ }
1950
+ try {
1951
+ const response = await this._request(`/projects/${projectId}/libraries`, {
1952
+ method: "GET",
1953
+ methodName: "getProjectLibraries"
1954
+ });
1955
+ if (response.success) {
1956
+ return response.data;
1957
+ }
1958
+ throw new Error(response.message);
1959
+ } catch (error) {
1960
+ throw new Error(`Failed to get project libraries: ${error.message}`, { cause: error });
1961
+ }
1962
+ }
1963
+ async addProjectLibraries(projectId, libraryIds) {
1964
+ this._requireReady("addProjectLibraries");
1965
+ if (!projectId || !libraryIds) {
1966
+ throw new Error("Project ID and library IDs are required");
1967
+ }
1968
+ try {
1969
+ const response = await this._request(`/projects/${projectId}/libraries`, {
1970
+ method: "POST",
1971
+ body: JSON.stringify({ libraryIds }),
1972
+ methodName: "addProjectLibraries"
1973
+ });
1974
+ if (response.success) {
1975
+ return response;
1976
+ }
1977
+ throw new Error(response.message);
1978
+ } catch (error) {
1979
+ throw new Error(`Failed to add project libraries: ${error.message}`, { cause: error });
1980
+ }
1981
+ }
1982
+ async removeProjectLibraries(projectId, libraryIds) {
1983
+ this._requireReady("removeProjectLibraries");
1984
+ if (!projectId || !libraryIds) {
1985
+ throw new Error("Project ID and library IDs are required");
1986
+ }
1987
+ try {
1988
+ const response = await this._request(`/projects/${projectId}/libraries`, {
1989
+ method: "DELETE",
1990
+ body: JSON.stringify({ libraryIds }),
1991
+ methodName: "removeProjectLibraries"
1992
+ });
1993
+ if (response.success) {
1994
+ return response;
1995
+ }
1996
+ throw new Error(response.message);
1997
+ } catch (error) {
1998
+ throw new Error(`Failed to remove project libraries: ${error.message}`, { cause: error });
1999
+ }
2000
+ }
2001
+ // ==================== PROJECT DATA METHODS (SYMSTORY REPLACEMENT) ====================
2002
+ /**
2003
+ * Apply changes to a project, creating a new version
2004
+ * Replaces: SymstoryService.updateData()
2005
+ */
2006
+ async applyProjectChanges(projectId, changes, options = {}) {
2007
+ this._requireReady("applyProjectChanges");
2008
+ if (!projectId) {
2009
+ throw new Error("Project ID is required");
2010
+ }
2011
+ if (!Array.isArray(changes)) {
2012
+ throw new Error("Changes must be an array");
2013
+ }
2014
+ const { message, branch = "main", type = "patch", headers } = options;
2015
+ const state = this._context && this._context.state;
2016
+ const { granularChanges, orders: preprocessorOrders } = preprocessChanges(state, changes, options);
2017
+ const derivedOrders = options.orders || (preprocessorOrders && preprocessorOrders.length ? preprocessorOrders : state ? computeOrdersForTuples(state, granularChanges) : []);
2018
+ const stringify = (val) => deepStringifyFunctions(val, Array.isArray(val) ? [] : {});
2019
+ try {
2020
+ const response = await this._request(`/projects/${projectId}/changes`, {
2021
+ method: "POST",
2022
+ body: JSON.stringify({
2023
+ changes: stringify(changes),
2024
+ granularChanges: stringify(granularChanges),
2025
+ message,
2026
+ branch,
2027
+ type,
2028
+ ...derivedOrders && derivedOrders.length ? { orders: derivedOrders } : {}
2029
+ }),
2030
+ ...headers ? { headers } : {},
2031
+ methodName: "applyProjectChanges"
2032
+ });
2033
+ if (response.success) {
2034
+ return response.data;
2035
+ }
2036
+ throw new Error(response.message);
2037
+ } catch (error) {
2038
+ throw new Error(`Failed to apply project changes: ${error.message}`, { cause: error });
2039
+ }
2040
+ }
2041
+ /**
2042
+ * Get current project data for a specific branch
2043
+ * Replaces: SymstoryService.getData()
2044
+ */
2045
+ async getProjectData(projectId, options = {}) {
2046
+ this._requireReady("getProjectData");
2047
+ if (!projectId) {
2048
+ throw new Error("Project ID is required");
2049
+ }
2050
+ const {
2051
+ branch = "main",
2052
+ version = "latest",
2053
+ includeHistory = false,
2054
+ headers
2055
+ } = options;
2056
+ const queryParams = new URLSearchParams({
2057
+ branch,
2058
+ version,
2059
+ includeHistory: includeHistory.toString()
2060
+ }).toString();
2061
+ try {
2062
+ const response = await this._request(
2063
+ `/projects/${projectId}/data?${queryParams}`,
2064
+ {
2065
+ method: "GET",
2066
+ methodName: "getProjectData",
2067
+ ...headers ? { headers } : {}
2068
+ }
2069
+ );
2070
+ if (response.success) {
2071
+ return response.data;
2072
+ }
2073
+ throw new Error(response.message);
2074
+ } catch (error) {
2075
+ throw new Error(`Failed to get project data: ${error.message}`, { cause: error });
2076
+ }
2077
+ }
2078
+ /**
2079
+ * Get project versions with pagination
2080
+ */
2081
+ async getProjectVersions(projectId, options = {}) {
2082
+ this._requireReady("getProjectVersions");
2083
+ if (!projectId) {
2084
+ throw new Error("Project ID is required");
2085
+ }
2086
+ const { branch = "main", page = 1, limit = 50, headers } = options;
2087
+ const queryParams = new URLSearchParams({
2088
+ branch,
2089
+ page: page.toString(),
2090
+ limit: limit.toString()
2091
+ }).toString();
2092
+ try {
2093
+ const response = await this._request(
2094
+ `/projects/${projectId}/versions?${queryParams}`,
2095
+ {
2096
+ method: "GET",
2097
+ methodName: "getProjectVersions",
2098
+ ...headers ? { headers } : {}
2099
+ }
2100
+ );
2101
+ if (response.success) {
2102
+ return response.data;
2103
+ }
2104
+ throw new Error(response.message);
2105
+ } catch (error) {
2106
+ throw new Error(`Failed to get project versions: ${error.message}`, { cause: error });
2107
+ }
2108
+ }
2109
+ // ==================== PROJECT ENVIRONMENT METHODS ====================
2110
+ /**
2111
+ * List all environments for a project along with plan limits and activation state.
2112
+ * Mirrors ProjectController.listEnvironments.
2113
+ */
2114
+ async listEnvironments(projectId, options = {}) {
2115
+ this._requireReady("listEnvironments");
2116
+ if (!projectId) {
2117
+ throw new Error("Project ID is required");
2118
+ }
2119
+ const { headers } = options;
2120
+ try {
2121
+ const response = await this._request(`/projects/${projectId}/environments`, {
2122
+ method: "GET",
2123
+ ...headers ? { headers } : {},
2124
+ methodName: "listEnvironments"
2125
+ });
2126
+ if (response && response.success) {
2127
+ return response.data;
2128
+ }
2129
+ throw new Error(response.message);
2130
+ } catch (error) {
2131
+ throw new Error(`Failed to list environments: ${error.message}`, { cause: error });
2132
+ }
2133
+ }
2134
+ /**
2135
+ * Activate multi-environment support for a project.
2136
+ * Optional `force` will reconfigure DNS/TLS even if already active.
2137
+ * Mirrors ProjectController.activateMultipleEnvironments.
2138
+ */
2139
+ async activateMultipleEnvironments(projectId, options = {}) {
2140
+ this._requireReady("activateMultipleEnvironments");
2141
+ if (!projectId) {
2142
+ throw new Error("Project ID is required");
2143
+ }
2144
+ const { force = false, headers } = options;
2145
+ try {
2146
+ const response = await this._request(`/projects/${projectId}/environments/activate`, {
2147
+ method: "POST",
2148
+ body: JSON.stringify({ ...force ? { force: true } : {} }),
2149
+ ...headers ? { headers } : {},
2150
+ methodName: "activateMultipleEnvironments"
2151
+ });
2152
+ if (response && response.success) {
2153
+ return response.data;
2154
+ }
2155
+ throw new Error(response.message);
2156
+ } catch (error) {
2157
+ throw new Error(
2158
+ `Failed to activate multiple environments: ${error.message}`,
2159
+ { cause: error }
2160
+ );
2161
+ }
2162
+ }
2163
+ /**
2164
+ * Create or update (upsert) an environment config for a project.
2165
+ * Mirrors ProjectController.upsertEnvironment.
2166
+ */
2167
+ async upsertEnvironment(projectId, envKey, config, options = {}) {
2168
+ this._requireReady("upsertEnvironment");
2169
+ if (!projectId) {
2170
+ throw new Error("Project ID is required");
2171
+ }
2172
+ if (!envKey) {
2173
+ throw new Error("Environment key is required");
2174
+ }
2175
+ if (!config || typeof config !== "object") {
2176
+ throw new Error("Environment config object is required");
2177
+ }
2178
+ const { headers } = options;
2179
+ try {
2180
+ const response = await this._request(`/projects/${projectId}/environments`, {
2181
+ method: "POST",
2182
+ body: JSON.stringify({ envKey, config }),
2183
+ ...headers ? { headers } : {},
2184
+ methodName: "upsertEnvironment"
2185
+ });
2186
+ if (response && response.success) {
2187
+ return response.data;
2188
+ }
2189
+ throw new Error(response.message);
2190
+ } catch (error) {
2191
+ throw new Error(`Failed to upsert environment: ${error.message}`, { cause: error });
2192
+ }
2193
+ }
2194
+ /**
2195
+ * Update an existing environment config.
2196
+ * Mirrors ProjectController.updateEnvironment.
2197
+ */
2198
+ async updateEnvironment(projectId, envKey, updates, options = {}) {
2199
+ this._requireReady("updateEnvironment");
2200
+ if (!projectId) {
2201
+ throw new Error("Project ID is required");
2202
+ }
2203
+ if (!envKey) {
2204
+ throw new Error("Environment key is required");
2205
+ }
2206
+ if (!updates || typeof updates !== "object") {
2207
+ throw new Error("Environment updates object is required");
2208
+ }
2209
+ const { headers } = options;
2210
+ try {
2211
+ const response = await this._request(
2212
+ `/projects/${projectId}/environments/${encodeURIComponent(envKey)}`,
2213
+ {
2214
+ method: "PATCH",
2215
+ body: JSON.stringify(updates),
2216
+ ...headers ? { headers } : {},
2217
+ methodName: "updateEnvironment"
2218
+ }
2219
+ );
2220
+ if (response && response.success) {
2221
+ return response.data;
2222
+ }
2223
+ throw new Error(response.message);
2224
+ } catch (error) {
2225
+ throw new Error(`Failed to update environment: ${error.message}`, { cause: error });
2226
+ }
2227
+ }
2228
+ /**
2229
+ * Publish a project to a specific environment (set its effective mode/version/branch).
2230
+ * Mirrors ProjectController.publishToEnvironment.
2231
+ */
2232
+ async publishToEnvironment(projectId, envKey, payload, options = {}) {
2233
+ this._requireReady("publishToEnvironment");
2234
+ if (!projectId) {
2235
+ throw new Error("Project ID is required");
2236
+ }
2237
+ if (!envKey) {
2238
+ throw new Error("Environment key is required");
2239
+ }
2240
+ if (!payload || typeof payload !== "object") {
2241
+ throw new Error("Publish payload is required");
2242
+ }
2243
+ const { headers } = options;
2244
+ try {
2245
+ const response = await this._request(
2246
+ `/projects/${projectId}/environments/${encodeURIComponent(envKey)}/publish`,
2247
+ {
2248
+ method: "POST",
2249
+ body: JSON.stringify(payload),
2250
+ ...headers ? { headers } : {},
2251
+ methodName: "publishToEnvironment"
2252
+ }
2253
+ );
2254
+ if (response && response.success) {
2255
+ return response.data;
2256
+ }
2257
+ throw new Error(response.message);
2258
+ } catch (error) {
2259
+ throw new Error(`Failed to publish to environment: ${error.message}`, { cause: error });
2260
+ }
2261
+ }
2262
+ /**
2263
+ * Delete an environment from a project.
2264
+ * Mirrors ProjectController.deleteEnvironment.
2265
+ */
2266
+ async deleteEnvironment(projectId, envKey, options = {}) {
2267
+ this._requireReady("deleteEnvironment");
2268
+ if (!projectId) {
2269
+ throw new Error("Project ID is required");
2270
+ }
2271
+ if (!envKey) {
2272
+ throw new Error("Environment key is required");
2273
+ }
2274
+ const { headers } = options;
2275
+ try {
2276
+ const response = await this._request(
2277
+ `/projects/${projectId}/environments/${encodeURIComponent(envKey)}`,
2278
+ {
2279
+ method: "DELETE",
2280
+ ...headers ? { headers } : {},
2281
+ methodName: "deleteEnvironment"
2282
+ }
2283
+ );
2284
+ if (response && response.success) {
2285
+ return response.data;
2286
+ }
2287
+ throw new Error(response.message);
2288
+ } catch (error) {
2289
+ throw new Error(`Failed to delete environment: ${error.message}`, { cause: error });
2290
+ }
2291
+ }
2292
+ /**
2293
+ * Promote content between environments (simple pipeline).
2294
+ * Mirrors ProjectController.promoteEnvironment.
2295
+ */
2296
+ async promoteEnvironment(projectId, fromEnvKey, toEnvKey, options = {}) {
2297
+ this._requireReady("promoteEnvironment");
2298
+ if (!projectId) {
2299
+ throw new Error("Project ID is required");
2300
+ }
2301
+ if (!fromEnvKey || !toEnvKey) {
2302
+ throw new Error("Both fromEnvKey and toEnvKey are required");
2303
+ }
2304
+ if (fromEnvKey === toEnvKey) {
2305
+ throw new Error("fromEnvKey and toEnvKey must be different");
2306
+ }
2307
+ const { headers } = options;
2308
+ try {
2309
+ const response = await this._request(`/projects/${projectId}/pipeline/promote`, {
2310
+ method: "POST",
2311
+ body: JSON.stringify({ from: fromEnvKey, to: toEnvKey }),
2312
+ ...headers ? { headers } : {},
2313
+ methodName: "promoteEnvironment"
2314
+ });
2315
+ if (response && response.success) {
2316
+ return response.data;
2317
+ }
2318
+ throw new Error(response.message);
2319
+ } catch (error) {
2320
+ throw new Error(`Failed to promote environment: ${error.message}`, { cause: error });
2321
+ }
2322
+ }
2323
+ /**
2324
+ * Restore project to a previous version
2325
+ * Replaces: SymstoryService.restoreVersion()
2326
+ */
2327
+ async restoreProjectVersion(projectId, version, options = {}) {
2328
+ this._requireReady("restoreProjectVersion");
2329
+ if (!projectId) {
2330
+ throw new Error("Project ID is required");
2331
+ }
2332
+ if (!version) {
2333
+ throw new Error("Version is required");
2334
+ }
2335
+ const { message, branch = "main", type = "patch", headers } = options;
2336
+ try {
2337
+ const response = await this._request(`/projects/${projectId}/restore`, {
2338
+ method: "POST",
2339
+ body: JSON.stringify({
2340
+ version,
2341
+ message,
2342
+ branch,
2343
+ type
2344
+ }),
2345
+ ...headers ? { headers } : {},
2346
+ methodName: "restoreProjectVersion"
2347
+ });
2348
+ if (response.success) {
2349
+ return response.data;
2350
+ }
2351
+ throw new Error(response.message);
2352
+ } catch (error) {
2353
+ throw new Error(`Failed to restore project version: ${error.message}`, { cause: error });
2354
+ }
2355
+ }
2356
+ /**
2357
+ * Helper method to update a single item in the project
2358
+ * Convenience wrapper around applyProjectChanges
2359
+ */
2360
+ async updateProjectItem(projectId, path, value, options = {}) {
2361
+ const changes = [["update", path, value]];
2362
+ const message = options.message || `Updated ${Array.isArray(path) ? path.join(".") : path}`;
2363
+ return await this.applyProjectChanges(projectId, changes, {
2364
+ ...options,
2365
+ message
2366
+ });
2367
+ }
2368
+ /**
2369
+ * Helper method to delete an item from the project
2370
+ * Convenience wrapper around applyProjectChanges
2371
+ */
2372
+ async deleteProjectItem(projectId, path, options = {}) {
2373
+ const changes = [["delete", path]];
2374
+ const message = options.message || `Deleted ${Array.isArray(path) ? path.join(".") : path}`;
2375
+ return await this.applyProjectChanges(projectId, changes, {
2376
+ ...options,
2377
+ message
2378
+ });
2379
+ }
2380
+ /**
2381
+ * Helper method to set a value in the project (alias for update)
2382
+ * Convenience wrapper around applyProjectChanges
2383
+ */
2384
+ async setProjectValue(projectId, path, value, options = {}) {
2385
+ const changes = [["set", path, value]];
2386
+ const message = options.message || `Set ${Array.isArray(path) ? path.join(".") : path}`;
2387
+ return await this.applyProjectChanges(projectId, changes, {
2388
+ ...options,
2389
+ message
2390
+ });
2391
+ }
2392
+ /**
2393
+ * Helper method to add multiple items to the project
2394
+ * Convenience wrapper around applyProjectChanges
2395
+ */
2396
+ async addProjectItems(projectId, items, options = {}) {
2397
+ const changes = items.map((item) => {
2398
+ const [type, data] = item;
2399
+ const { value, ...schema } = data;
2400
+ return [
2401
+ ["update", [type, data.key], value],
2402
+ ["update", ["schema", type, data.key], schema]
2403
+ ];
2404
+ }).flat();
2405
+ const message = options.message || `Added ${items.length} items`;
2406
+ return await this.applyProjectChanges(projectId, changes, {
2407
+ ...options,
2408
+ message
2409
+ });
2410
+ }
2411
+ /**
2412
+ * Helper method to get specific data from project by path
2413
+ * Convenience wrapper that gets project data and extracts specific path
2414
+ */
2415
+ async getProjectItemByPath(projectId, path, options = {}) {
2416
+ const projectData = await this.getProjectData(projectId, options);
2417
+ if (!(projectData == null ? void 0 : projectData.data)) {
2418
+ return null;
2419
+ }
2420
+ let current = projectData.data;
2421
+ const pathArray = Array.isArray(path) ? path : [path];
2422
+ for (const segment of pathArray) {
2423
+ if (current && typeof current === "object" && segment in current) {
2424
+ current = current[segment];
2425
+ } else {
2426
+ return null;
2427
+ }
2428
+ }
2429
+ return current;
2430
+ }
2431
+ // ==================== FAVORITE PROJECT METHODS ====================
2432
+ async getFavoriteProjects() {
2433
+ this._requireReady("getFavoriteProjects");
2434
+ try {
2435
+ const response = await this._request("/users/favorites", {
2436
+ method: "GET",
2437
+ methodName: "getFavoriteProjects"
2438
+ });
2439
+ if (response.success) {
2440
+ return (response.data || []).map((project) => ({
2441
+ isFavorite: true,
2442
+ ...project,
2443
+ ...project.icon && {
2444
+ icon: {
2445
+ src: `${this._apiUrl}/core/files/public/${project.icon.id}/download`,
2446
+ ...project.icon
2447
+ }
2448
+ }
2449
+ }));
2450
+ }
2451
+ throw new Error(response.message);
2452
+ } catch (error) {
2453
+ throw new Error(`Failed to get favorite projects: ${error.message}`, { cause: error });
2454
+ }
2455
+ }
2456
+ async addFavoriteProject(projectId) {
2457
+ this._requireReady("addFavoriteProject");
2458
+ if (!projectId) {
2459
+ throw new Error("Project ID is required");
2460
+ }
2461
+ try {
2462
+ const response = await this._request(`/users/favorites/${projectId}`, {
2463
+ method: "POST",
2464
+ methodName: "addFavoriteProject"
2465
+ });
2466
+ if (response.success) {
2467
+ return response.data;
2468
+ }
2469
+ throw new Error(response.message);
2470
+ } catch (error) {
2471
+ throw new Error(`Failed to add favorite project: ${error.message}`, { cause: error });
2472
+ }
2473
+ }
2474
+ async removeFavoriteProject(projectId) {
2475
+ this._requireReady("removeFavoriteProject");
2476
+ if (!projectId) {
2477
+ throw new Error("Project ID is required");
2478
+ }
2479
+ try {
2480
+ const response = await this._request(`/users/favorites/${projectId}`, {
2481
+ method: "DELETE",
2482
+ methodName: "removeFavoriteProject"
2483
+ });
2484
+ if (response.success) {
2485
+ return response.message || "Project removed from favorites";
2486
+ }
2487
+ throw new Error(response.message);
2488
+ } catch (error) {
2489
+ throw new Error(`Failed to remove favorite project: ${error.message}`, { cause: error });
2490
+ }
2491
+ }
2492
+ // ==================== RECENT PROJECT METHODS ====================
2493
+ async getRecentProjects(options = {}) {
2494
+ this._requireReady("getRecentProjects");
2495
+ const { limit = 20, headers } = options;
2496
+ const queryString = new URLSearchParams({
2497
+ limit: limit.toString()
2498
+ }).toString();
2499
+ const url = `/users/projects/recent${queryString ? `?${queryString}` : ""}`;
2500
+ try {
2501
+ const response = await this._request(url, {
2502
+ method: "GET",
2503
+ ...headers ? { headers } : {},
2504
+ methodName: "getRecentProjects"
2505
+ });
2506
+ if (response.success) {
2507
+ return (response.data || []).map((item) => ({
2508
+ ...item.project,
2509
+ ...item.project && item.project.icon && {
2510
+ icon: {
2511
+ src: `${this._apiUrl}/core/files/public/${item.project.icon.id}/download`,
2512
+ ...item.project.icon
2513
+ }
2514
+ }
2515
+ }));
2516
+ }
2517
+ throw new Error(response.message);
2518
+ } catch (error) {
2519
+ throw new Error(`Failed to get recent projects: ${error.message}`, { cause: error });
2520
+ }
2521
+ }
2522
+ };
2523
+ export {
2524
+ ProjectService
2525
+ };
2526
+ // @preserve-env