@symbo.ls/sdk 2.32.2 → 2.32.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/config/environment.js +43 -8
- package/dist/cjs/index.js +12 -4
- package/dist/cjs/services/AdminService.js +4 -4
- package/dist/cjs/services/AuthService.js +36 -149
- package/dist/cjs/services/BaseService.js +5 -18
- package/dist/cjs/services/BranchService.js +10 -10
- package/dist/cjs/services/CollabService.js +94 -61
- package/dist/cjs/services/CoreService.js +19 -19
- package/dist/cjs/services/DnsService.js +4 -4
- package/dist/cjs/services/FileService.js +2 -2
- package/dist/cjs/services/PaymentService.js +2 -2
- package/dist/cjs/services/PlanService.js +12 -12
- package/dist/cjs/services/ProjectService.js +45 -35
- package/dist/cjs/services/PullRequestService.js +7 -7
- package/dist/cjs/services/ScreenshotService.js +304 -0
- package/dist/cjs/services/SubscriptionService.js +14 -14
- package/dist/cjs/services/index.js +4 -0
- package/dist/cjs/utils/TokenManager.js +16 -5
- package/dist/cjs/utils/changePreprocessor.js +134 -0
- package/dist/cjs/utils/jsonDiff.js +46 -4
- package/dist/cjs/utils/ordering.js +274 -0
- package/dist/cjs/utils/services.js +14 -1
- package/dist/esm/config/environment.js +43 -8
- package/dist/esm/index.js +1099 -417
- package/dist/esm/services/AdminService.js +68 -35
- package/dist/esm/services/AuthService.js +100 -168
- package/dist/esm/services/BaseService.js +64 -31
- package/dist/esm/services/BranchService.js +74 -41
- package/dist/esm/services/CollabService.js +570 -97
- package/dist/esm/services/CoreService.js +83 -50
- package/dist/esm/services/DnsService.js +68 -35
- package/dist/esm/services/FileService.js +66 -33
- package/dist/esm/services/PaymentService.js +66 -33
- package/dist/esm/services/PlanService.js +76 -43
- package/dist/esm/services/ProjectService.js +547 -66
- package/dist/esm/services/PullRequestService.js +71 -38
- package/dist/esm/services/ScreenshotService.js +992 -0
- package/dist/esm/services/SubscriptionService.js +78 -45
- package/dist/esm/services/index.js +1076 -412
- package/dist/esm/utils/CollabClient.js +89 -12
- package/dist/esm/utils/TokenManager.js +16 -5
- package/dist/esm/utils/changePreprocessor.js +442 -0
- package/dist/esm/utils/jsonDiff.js +46 -4
- package/dist/esm/utils/ordering.js +256 -0
- package/dist/esm/utils/services.js +14 -1
- package/dist/node/config/environment.js +43 -8
- package/dist/node/index.js +14 -5
- package/dist/node/services/AdminService.js +4 -4
- package/dist/node/services/AuthService.js +36 -139
- package/dist/node/services/BaseService.js +5 -18
- package/dist/node/services/BranchService.js +10 -10
- package/dist/node/services/CollabService.js +95 -62
- package/dist/node/services/CoreService.js +19 -19
- package/dist/node/services/DnsService.js +4 -4
- package/dist/node/services/FileService.js +2 -2
- package/dist/node/services/PaymentService.js +2 -2
- package/dist/node/services/PlanService.js +12 -12
- package/dist/node/services/ProjectService.js +45 -35
- package/dist/node/services/PullRequestService.js +7 -7
- package/dist/node/services/ScreenshotService.js +285 -0
- package/dist/node/services/SubscriptionService.js +14 -14
- package/dist/node/services/index.js +4 -0
- package/dist/node/utils/TokenManager.js +16 -5
- package/dist/node/utils/changePreprocessor.js +115 -0
- package/dist/node/utils/jsonDiff.js +46 -4
- package/dist/node/utils/ordering.js +255 -0
- package/dist/node/utils/services.js +14 -1
- package/package.json +7 -6
- package/src/config/environment.js +48 -9
- package/src/index.js +38 -22
- package/src/services/AdminService.js +4 -4
- package/src/services/AuthService.js +42 -175
- package/src/services/BaseService.js +7 -24
- package/src/services/BranchService.js +10 -10
- package/src/services/CollabService.js +115 -74
- package/src/services/CoreService.js +19 -19
- package/src/services/DnsService.js +4 -4
- package/src/services/FileService.js +2 -2
- package/src/services/PaymentService.js +2 -2
- package/src/services/PlanService.js +12 -12
- package/src/services/ProjectService.js +50 -35
- package/src/services/PullRequestService.js +7 -7
- package/src/services/ScreenshotService.js +258 -0
- package/src/services/SubscriptionService.js +14 -14
- package/src/services/index.js +6 -1
- package/src/utils/TokenManager.js +19 -5
- package/src/utils/changePreprocessor.js +139 -0
- package/src/utils/jsonDiff.js +40 -5
- package/src/utils/ordering.js +244 -0
- package/src/utils/services.js +15 -1
|
@@ -39,7 +39,7 @@ export class SubscriptionService extends BaseService {
|
|
|
39
39
|
}
|
|
40
40
|
throw new Error(response.message)
|
|
41
41
|
} catch (error) {
|
|
42
|
-
throw new Error(`Failed to create subscription: ${error.message}
|
|
42
|
+
throw new Error(`Failed to create subscription: ${error.message}`, { cause: error })
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -62,7 +62,7 @@ export class SubscriptionService extends BaseService {
|
|
|
62
62
|
}
|
|
63
63
|
throw new Error(response.message)
|
|
64
64
|
} catch (error) {
|
|
65
|
-
throw new Error(`Failed to get project subscription status: ${error.message}
|
|
65
|
+
throw new Error(`Failed to get project subscription status: ${error.message}`, { cause: error })
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -85,7 +85,7 @@ export class SubscriptionService extends BaseService {
|
|
|
85
85
|
}
|
|
86
86
|
throw new Error(response.message)
|
|
87
87
|
} catch (error) {
|
|
88
|
-
throw new Error(`Failed to get subscription usage: ${error.message}
|
|
88
|
+
throw new Error(`Failed to get subscription usage: ${error.message}`, { cause: error })
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -108,7 +108,7 @@ export class SubscriptionService extends BaseService {
|
|
|
108
108
|
}
|
|
109
109
|
throw new Error(response.message)
|
|
110
110
|
} catch (error) {
|
|
111
|
-
throw new Error(`Failed to cancel subscription: ${error.message}
|
|
111
|
+
throw new Error(`Failed to cancel subscription: ${error.message}`, { cause: error })
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -142,7 +142,7 @@ export class SubscriptionService extends BaseService {
|
|
|
142
142
|
}
|
|
143
143
|
throw new Error(response.message)
|
|
144
144
|
} catch (error) {
|
|
145
|
-
throw new Error(`Failed to list invoices: ${error.message}
|
|
145
|
+
throw new Error(`Failed to list invoices: ${error.message}`, { cause: error })
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -171,7 +171,7 @@ export class SubscriptionService extends BaseService {
|
|
|
171
171
|
}
|
|
172
172
|
throw new Error(response.message)
|
|
173
173
|
} catch (error) {
|
|
174
|
-
throw new Error(`Failed to get portal URL: ${error.message}
|
|
174
|
+
throw new Error(`Failed to get portal URL: ${error.message}`, { cause: error })
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -216,7 +216,7 @@ export class SubscriptionService extends BaseService {
|
|
|
216
216
|
const status = await this.getProjectStatus(projectId)
|
|
217
217
|
return status.hasSubscription === true
|
|
218
218
|
} catch (error) {
|
|
219
|
-
throw new Error(`Failed to check subscription status: ${error.message}
|
|
219
|
+
throw new Error(`Failed to check subscription status: ${error.message}`, { cause: error })
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -231,7 +231,7 @@ export class SubscriptionService extends BaseService {
|
|
|
231
231
|
}
|
|
232
232
|
return status.subscription
|
|
233
233
|
} catch (error) {
|
|
234
|
-
throw new Error(`Failed to get project subscription: ${error.message}
|
|
234
|
+
throw new Error(`Failed to get project subscription: ${error.message}`, { cause: error })
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -246,7 +246,7 @@ export class SubscriptionService extends BaseService {
|
|
|
246
246
|
}
|
|
247
247
|
return status.usage
|
|
248
248
|
} catch (error) {
|
|
249
|
-
throw new Error(`Failed to get project usage: ${error.message}
|
|
249
|
+
throw new Error(`Failed to get project usage: ${error.message}`, { cause: error })
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -261,7 +261,7 @@ export class SubscriptionService extends BaseService {
|
|
|
261
261
|
pagination: result.pagination || {}
|
|
262
262
|
}
|
|
263
263
|
} catch (error) {
|
|
264
|
-
throw new Error(`Failed to get invoices with pagination: ${error.message}
|
|
264
|
+
throw new Error(`Failed to get invoices with pagination: ${error.message}`, { cause: error })
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -275,7 +275,7 @@ export class SubscriptionService extends BaseService {
|
|
|
275
275
|
// You might need to adjust based on your backend response
|
|
276
276
|
return usage && usage.subscription && usage.subscription.status === 'active'
|
|
277
277
|
} catch (error) {
|
|
278
|
-
throw new Error(`Failed to check subscription status: ${error.message}
|
|
278
|
+
throw new Error(`Failed to check subscription status: ${error.message}`, { cause: error })
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -287,7 +287,7 @@ export class SubscriptionService extends BaseService {
|
|
|
287
287
|
const usage = await this.getUsage(subscriptionId)
|
|
288
288
|
return usage.limits || {}
|
|
289
289
|
} catch (error) {
|
|
290
|
-
throw new Error(`Failed to get subscription limits: ${error.message}
|
|
290
|
+
throw new Error(`Failed to get subscription limits: ${error.message}`, { cause: error })
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -327,7 +327,7 @@ export class SubscriptionService extends BaseService {
|
|
|
327
327
|
}
|
|
328
328
|
throw new Error(response.message)
|
|
329
329
|
} catch (error) {
|
|
330
|
-
throw new Error(`Failed to change subscription: ${error.message}
|
|
330
|
+
throw new Error(`Failed to change subscription: ${error.message}`, { cause: error })
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -359,7 +359,7 @@ export class SubscriptionService extends BaseService {
|
|
|
359
359
|
}
|
|
360
360
|
throw new Error(response.message)
|
|
361
361
|
} catch (error) {
|
|
362
|
-
throw new Error(`Failed to downgrade subscription: ${error.message}
|
|
362
|
+
throw new Error(`Failed to downgrade subscription: ${error.message}`, { cause: error })
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
package/src/services/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { DnsService } from './DnsService.js'
|
|
|
11
11
|
import { BranchService } from './BranchService.js'
|
|
12
12
|
import { PullRequestService } from './PullRequestService.js'
|
|
13
13
|
import { AdminService } from './AdminService.js'
|
|
14
|
+
import { ScreenshotService } from './ScreenshotService.js'
|
|
14
15
|
|
|
15
16
|
const createService = (ServiceClass, config) => new ServiceClass(config)
|
|
16
17
|
|
|
@@ -49,6 +50,9 @@ export const createPullRequestService = config =>
|
|
|
49
50
|
export const createAdminService = config =>
|
|
50
51
|
createService(AdminService, config)
|
|
51
52
|
|
|
53
|
+
export const createScreenshotService = config =>
|
|
54
|
+
createService(ScreenshotService, config)
|
|
55
|
+
|
|
52
56
|
export {
|
|
53
57
|
AuthService,
|
|
54
58
|
CoreService,
|
|
@@ -61,5 +65,6 @@ export {
|
|
|
61
65
|
DnsService,
|
|
62
66
|
BranchService,
|
|
63
67
|
PullRequestService,
|
|
64
|
-
AdminService
|
|
68
|
+
AdminService,
|
|
69
|
+
ScreenshotService
|
|
65
70
|
}
|
|
@@ -6,7 +6,7 @@ export class TokenManager {
|
|
|
6
6
|
constructor (options = {}) {
|
|
7
7
|
this.config = {
|
|
8
8
|
storagePrefix: 'symbols_',
|
|
9
|
-
storageType: (typeof window === 'undefined' || process.env.NODE_ENV === 'test') ? 'memory' : 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
|
|
9
|
+
storageType: (typeof window === 'undefined' || process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'testing') ? 'memory' : 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
|
|
10
10
|
refreshBuffer: 60 * 1000, // Refresh 1 minute before expiry
|
|
11
11
|
maxRetries: 3,
|
|
12
12
|
apiUrl: options.apiUrl || '/api',
|
|
@@ -52,16 +52,30 @@ export class TokenManager {
|
|
|
52
52
|
return this._memoryStorage
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
const
|
|
55
|
+
// Guard against environments where accessing storage throws (e.g., opaque origins)
|
|
56
|
+
const safeGetStorage = (provider) => {
|
|
57
|
+
try {
|
|
58
|
+
const storage = provider()
|
|
59
|
+
// Try a simple set/remove cycle to ensure it is usable
|
|
60
|
+
const testKey = `${this.config.storagePrefix}__tm_test__`
|
|
61
|
+
storage.setItem(testKey, '1')
|
|
62
|
+
storage.removeItem(testKey)
|
|
63
|
+
return storage
|
|
64
|
+
} catch {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const localStorageInstance = safeGetStorage(() => window.localStorage)
|
|
70
|
+
const sessionStorageInstance = safeGetStorage(() => window.sessionStorage)
|
|
57
71
|
|
|
58
72
|
switch (this.config.storageType) {
|
|
59
73
|
case 'sessionStorage':
|
|
60
|
-
return
|
|
74
|
+
return sessionStorageInstance || this._memoryStorage
|
|
61
75
|
case 'memory':
|
|
62
76
|
return this._memoryStorage
|
|
63
77
|
default:
|
|
64
|
-
return
|
|
78
|
+
return localStorageInstance || this._memoryStorage
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
|
|
2
|
+
import { diffJson } from './jsonDiff.js'
|
|
3
|
+
import { computeOrdersForTuples } from './ordering.js'
|
|
4
|
+
|
|
5
|
+
function isPlainObject (val) {
|
|
6
|
+
return val && typeof val === 'object' && !Array.isArray(val)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getByPathSafe (root, path) {
|
|
10
|
+
if (!root || typeof root.getByPath !== 'function') { return null }
|
|
11
|
+
try { return root.getByPath(path) } catch { return null }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Preprocess broad project changes into granular changes and ordering metadata.
|
|
16
|
+
* - Expands top-level object updates (e.g. ['update', ['components'], {...}])
|
|
17
|
+
* into fine-grained ['update'|'delete', [...], value] tuples using a diff
|
|
18
|
+
* against the current state when available
|
|
19
|
+
* - Preserves schema paths as-is
|
|
20
|
+
* - Filters out explicit deletes targeting __order keys
|
|
21
|
+
* - Appends any extra tuples from options.append
|
|
22
|
+
* - Computes stable orders for impacted parent containers
|
|
23
|
+
*/
|
|
24
|
+
export function preprocessChanges (root, tuples = [], options = {}) {
|
|
25
|
+
const expandTuple = (t) => {
|
|
26
|
+
const [action, path, value] = t || []
|
|
27
|
+
const isSchemaPath = Array.isArray(path) && path[0] === 'schema'
|
|
28
|
+
if (action === 'delete') { return [t] }
|
|
29
|
+
|
|
30
|
+
const canConsiderExpansion = (
|
|
31
|
+
action === 'update' &&
|
|
32
|
+
Array.isArray(path) &&
|
|
33
|
+
(
|
|
34
|
+
path.length === 1 ||
|
|
35
|
+
path.length === 2 ||
|
|
36
|
+
(isSchemaPath && path.length === 3)
|
|
37
|
+
) &&
|
|
38
|
+
isPlainObject(value)
|
|
39
|
+
)
|
|
40
|
+
if (!canConsiderExpansion) { return [t] }
|
|
41
|
+
|
|
42
|
+
const prev = getByPathSafe(root, path) || {}
|
|
43
|
+
const next = value || {}
|
|
44
|
+
if (!isPlainObject(prev) || !isPlainObject(next)) { return [t] }
|
|
45
|
+
|
|
46
|
+
const ops = diffJson(prev, next, [])
|
|
47
|
+
// If diff yields no nested ops, preserve the original tuple as a fallback
|
|
48
|
+
// (e.g. when value equality or missing previous state prevents expansion).
|
|
49
|
+
if (!ops.length) { return [t] }
|
|
50
|
+
|
|
51
|
+
const out = []
|
|
52
|
+
for (let i = 0; i < ops.length; i++) {
|
|
53
|
+
const op = ops[i]
|
|
54
|
+
const fullPath = [...path, ...op.path]
|
|
55
|
+
const last = fullPath[fullPath.length - 1]
|
|
56
|
+
if (op.action === 'set') {
|
|
57
|
+
out.push(['update', fullPath, op.value])
|
|
58
|
+
} else if (op.action === 'del') {
|
|
59
|
+
if (last !== '__order') { out.push(['delete', fullPath]) }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Prefer granular leaf operations only to minimize payload duplication.
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const minimizeTuples = (input) => {
|
|
67
|
+
const out = []
|
|
68
|
+
const seen = new Set()
|
|
69
|
+
for (let i = 0; i < input.length; i++) {
|
|
70
|
+
const expanded = expandTuple(input[i])
|
|
71
|
+
for (let k = 0; k < expanded.length; k++) {
|
|
72
|
+
const tuple = expanded[k]
|
|
73
|
+
const isDelete = Array.isArray(tuple) && tuple[0] === 'delete'
|
|
74
|
+
const isOrderKey = (
|
|
75
|
+
isDelete &&
|
|
76
|
+
Array.isArray(tuple[1]) &&
|
|
77
|
+
tuple[1][tuple[1].length - 1] === '__order'
|
|
78
|
+
)
|
|
79
|
+
if (!isOrderKey) {
|
|
80
|
+
const key = JSON.stringify(tuple)
|
|
81
|
+
if (!seen.has(key)) { seen.add(key); out.push(tuple) }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const granularChanges = (() => {
|
|
89
|
+
try {
|
|
90
|
+
const res = minimizeTuples(tuples)
|
|
91
|
+
if (options.append && options.append.length) { res.push(...options.append) }
|
|
92
|
+
return res
|
|
93
|
+
} catch {
|
|
94
|
+
// Fallback to original tuples if anything goes wrong
|
|
95
|
+
return Array.isArray(tuples) ? tuples.slice() : []
|
|
96
|
+
}
|
|
97
|
+
})()
|
|
98
|
+
|
|
99
|
+
// Base orders from granular changes/state
|
|
100
|
+
const baseOrders = computeOrdersForTuples(root, granularChanges)
|
|
101
|
+
|
|
102
|
+
// Prefer explicit order for containers updated via ['update', [type], value] or ['update', [type, key], value]
|
|
103
|
+
const preferOrdersMap = new Map()
|
|
104
|
+
for (let i = 0; i < tuples.length; i++) {
|
|
105
|
+
const t = tuples[i]
|
|
106
|
+
if (!Array.isArray(t) || t.length < 3) {
|
|
107
|
+
// eslint-disable-next-line no-continue
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
const [action, path, value] = t
|
|
111
|
+
if (
|
|
112
|
+
action !== 'update' ||
|
|
113
|
+
!Array.isArray(path) ||
|
|
114
|
+
(path.length !== 1 && path.length !== 2) ||
|
|
115
|
+
!isPlainObject(value)
|
|
116
|
+
) {
|
|
117
|
+
// eslint-disable-next-line no-continue
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
const keys = Object.keys(value).filter(k => k !== '__order')
|
|
121
|
+
const key = JSON.stringify(path)
|
|
122
|
+
preferOrdersMap.set(key, { path, keys })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const mergedOrders = []
|
|
126
|
+
const seen = new Set()
|
|
127
|
+
// Add preferred top-level orders first
|
|
128
|
+
preferOrdersMap.forEach((v, k) => { seen.add(k); mergedOrders.push(v) })
|
|
129
|
+
// Add remaining base orders
|
|
130
|
+
for (let i = 0; i < baseOrders.length; i++) {
|
|
131
|
+
const v = baseOrders[i]
|
|
132
|
+
const k = JSON.stringify(v.path)
|
|
133
|
+
if (!seen.has(k)) { seen.add(k); mergedOrders.push(v) }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { granularChanges, orders: mergedOrders }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
package/src/utils/jsonDiff.js
CHANGED
|
@@ -7,12 +7,47 @@ function isPlainObject (o) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function deepEqual (a, b) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// Fast path for strict equality (handles primitives and same refs)
|
|
11
|
+
if (Object.is(a, b)) { return true }
|
|
12
|
+
|
|
13
|
+
// Functions: compare source text to detect semantic change
|
|
14
|
+
if (typeof a === 'function' && typeof b === 'function') {
|
|
15
|
+
try { return a.toString() === b.toString() } catch { return false }
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
// One is function and the other is not
|
|
19
|
+
if (typeof a === 'function' || typeof b === 'function') { return false }
|
|
20
|
+
|
|
21
|
+
// Dates
|
|
22
|
+
if (a instanceof Date && b instanceof Date) { return a.getTime() === b.getTime() }
|
|
23
|
+
|
|
24
|
+
// RegExp
|
|
25
|
+
if (a instanceof RegExp && b instanceof RegExp) { return String(a) === String(b) }
|
|
26
|
+
|
|
27
|
+
// Arrays
|
|
28
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
29
|
+
if (a.length !== b.length) { return false }
|
|
30
|
+
for (let i = 0; i < a.length; i++) {
|
|
31
|
+
if (!deepEqual(a[i], b[i])) { return false }
|
|
32
|
+
}
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Objects (including plain objects when we get here)
|
|
37
|
+
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
38
|
+
const aKeys = Object.keys(a)
|
|
39
|
+
const bKeys = Object.keys(b)
|
|
40
|
+
if (aKeys.length !== bKeys.length) { return false }
|
|
41
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
42
|
+
const key = aKeys[i]
|
|
43
|
+
if (!Object.hasOwn(b, key)) { return false }
|
|
44
|
+
if (!deepEqual(a[key], b[key])) { return false }
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Fallback for different types
|
|
50
|
+
return false
|
|
16
51
|
}
|
|
17
52
|
|
|
18
53
|
import * as Y from 'yjs'
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/* eslint-disable no-continue */
|
|
2
|
+
// Utilities to compute stable key ordering for parent objects impacted by changes
|
|
3
|
+
|
|
4
|
+
function isObjectLike (val) {
|
|
5
|
+
return val && typeof val === 'object' && !Array.isArray(val)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizePath (path) {
|
|
9
|
+
if (Array.isArray(path)) {return path}
|
|
10
|
+
if (typeof path === 'string') {return [path]}
|
|
11
|
+
return []
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getParentPathsFromTuples (tuples = []) {
|
|
15
|
+
const seen = new Set()
|
|
16
|
+
const parents = []
|
|
17
|
+
const META_KEYS = new Set([
|
|
18
|
+
'style', 'class', 'text', 'html', 'content', 'data', 'attr', 'state', 'scope',
|
|
19
|
+
'define', 'on', 'extend', 'extends', 'childExtend', 'childExtends',
|
|
20
|
+
'children', 'component', 'context', 'tag', 'key', '__order', 'if'
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < tuples.length; i++) {
|
|
24
|
+
const tuple = tuples[i]
|
|
25
|
+
if (!Array.isArray(tuple) || tuple.length < 2) {continue}
|
|
26
|
+
const path = normalizePath(tuple[1])
|
|
27
|
+
if (!path.length) {continue}
|
|
28
|
+
// Ignore schema containers entirely for parent order targets
|
|
29
|
+
if (path[0] === 'schema') {continue}
|
|
30
|
+
const immediateParent = path.slice(0, -1)
|
|
31
|
+
if (immediateParent.length) {
|
|
32
|
+
const key = JSON.stringify(immediateParent)
|
|
33
|
+
if (!seen.has(key)) {
|
|
34
|
+
seen.add(key)
|
|
35
|
+
parents.push(immediateParent)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// If the tuple points to a meta key (e.g. props/text), also include the container parent
|
|
40
|
+
const last = path[path.length - 1]
|
|
41
|
+
if (META_KEYS.has(last) && path.length >= 2) {
|
|
42
|
+
const containerParent = path.slice(0, -2)
|
|
43
|
+
if (containerParent.length) {
|
|
44
|
+
const key2 = JSON.stringify(containerParent)
|
|
45
|
+
if (!seen.has(key2)) {
|
|
46
|
+
seen.add(key2)
|
|
47
|
+
parents.push(containerParent)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Additionally include container parents for any meta segment in the path
|
|
52
|
+
for (let j = 0; j < path.length; j++) {
|
|
53
|
+
const seg = path[j]
|
|
54
|
+
if (!META_KEYS.has(seg)) { continue }
|
|
55
|
+
const containerParent2 = path.slice(0, j)
|
|
56
|
+
if (!containerParent2.length) { continue }
|
|
57
|
+
const key3 = JSON.stringify(containerParent2)
|
|
58
|
+
if (!seen.has(key3)) {
|
|
59
|
+
seen.add(key3)
|
|
60
|
+
parents.push(containerParent2)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return parents
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Compute ordered key arrays for each parent path using the provided root state's getByPath.
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} root - Root state with a getByPath(pathArray) method
|
|
72
|
+
* @param {Array<Array<string>>} parentPaths - Array of parent paths to inspect
|
|
73
|
+
* @returns {Array<{ path: string[], keys: string[] }>} orders
|
|
74
|
+
*/
|
|
75
|
+
export function computeOrdersFromState (root, parentPaths = []) {
|
|
76
|
+
if (!root || typeof root.getByPath !== 'function') {return []}
|
|
77
|
+
|
|
78
|
+
const orders = []
|
|
79
|
+
const EXCLUDE_KEYS = new Set(['__order'])
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < parentPaths.length; i++) {
|
|
82
|
+
const parentPath = parentPaths[i]
|
|
83
|
+
const obj = (() => {
|
|
84
|
+
try { return root.getByPath(parentPath) } catch { return null }
|
|
85
|
+
})()
|
|
86
|
+
|
|
87
|
+
if (!isObjectLike(obj)) {continue}
|
|
88
|
+
|
|
89
|
+
const keys = Object.keys(obj).filter(k => !EXCLUDE_KEYS.has(k))
|
|
90
|
+
orders.push({ path: parentPath, keys })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return orders
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convenience helper to derive orders directly from tuples and a root state.
|
|
98
|
+
*/
|
|
99
|
+
// --- Schema `code` parsing helpers ---
|
|
100
|
+
function normaliseSchemaCode (code) {
|
|
101
|
+
if (typeof code !== 'string' || !code.length) { return '' }
|
|
102
|
+
// Replace custom placeholders back to actual characters
|
|
103
|
+
return code
|
|
104
|
+
.replaceAll('/////n', '\n')
|
|
105
|
+
.replaceAll('/////tilde', '`')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseExportedObject (code) {
|
|
109
|
+
const src = normaliseSchemaCode(code)
|
|
110
|
+
if (!src) { return null }
|
|
111
|
+
const body = src.replace(/^\s*export\s+default\s*/u, 'return ')
|
|
112
|
+
try {
|
|
113
|
+
// eslint-disable-next-line no-new-func
|
|
114
|
+
return new Function(body)()
|
|
115
|
+
} catch {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function extractTopLevelKeysFromCode (code) {
|
|
121
|
+
const obj = parseExportedObject(code)
|
|
122
|
+
if (!obj || typeof obj !== 'object') { return [] }
|
|
123
|
+
return Object.keys(obj)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function computeOrdersForTuples (root, tuples = []) {
|
|
127
|
+
// Pre-scan tuples to collect child keys that will be added/updated for each
|
|
128
|
+
// data container (e.g. ['pages', '/']). This lets us include keys created in
|
|
129
|
+
// the same batch even if they are not yet present in the state object when
|
|
130
|
+
// we compute orders.
|
|
131
|
+
const pendingChildrenByContainer = new Map()
|
|
132
|
+
for (let i = 0; i < tuples.length; i++) {
|
|
133
|
+
const t = tuples[i]
|
|
134
|
+
if (!Array.isArray(t)) { continue }
|
|
135
|
+
const [action, path] = t
|
|
136
|
+
const p = normalizePath(path)
|
|
137
|
+
if (!Array.isArray(p) || p.length < 3) { continue }
|
|
138
|
+
// Ignore schema edits here – we want actual data container child keys
|
|
139
|
+
if (p[0] === 'schema') { continue }
|
|
140
|
+
const [typeName, containerKey, childKey] = p
|
|
141
|
+
const containerPath = [typeName, containerKey]
|
|
142
|
+
const key = JSON.stringify(containerPath)
|
|
143
|
+
if (!pendingChildrenByContainer.has(key)) {
|
|
144
|
+
pendingChildrenByContainer.set(key, new Set())
|
|
145
|
+
}
|
|
146
|
+
// We only track updates/sets; deletes need not appear in the desired order
|
|
147
|
+
if (action === 'update' || action === 'set') {
|
|
148
|
+
pendingChildrenByContainer.get(key).add(childKey)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 1) Prefer code-derived order for corresponding data container when schema 'code' present
|
|
153
|
+
const preferredOrderMap = new Map()
|
|
154
|
+
for (let i = 0; i < tuples.length; i++) {
|
|
155
|
+
const t = tuples[i]
|
|
156
|
+
if (!Array.isArray(t)) {continue}
|
|
157
|
+
const [action, path, value] = t
|
|
158
|
+
const p = normalizePath(path)
|
|
159
|
+
if (action !== 'update' || !Array.isArray(p) || p.length < 3) {continue}
|
|
160
|
+
if (p[0] !== 'schema') {continue}
|
|
161
|
+
const [, type, key] = p
|
|
162
|
+
const containerPath = [type, key]
|
|
163
|
+
const uses = value && Array.isArray(value.uses) ? value.uses : null
|
|
164
|
+
const code = value && value.code
|
|
165
|
+
|
|
166
|
+
// Resolve present keys from state
|
|
167
|
+
const obj = (() => {
|
|
168
|
+
try { return root && typeof root.getByPath === 'function' ? root.getByPath(containerPath) : null } catch { return null }
|
|
169
|
+
})()
|
|
170
|
+
if (!obj) {continue}
|
|
171
|
+
const present = new Set(Object.keys(obj))
|
|
172
|
+
const EXCLUDE_KEYS = new Set(['__order'])
|
|
173
|
+
|
|
174
|
+
// Try to parse key order from schema.code
|
|
175
|
+
const codeKeys = extractTopLevelKeysFromCode(code)
|
|
176
|
+
let resolved = []
|
|
177
|
+
// Keys eligible for ordering are those already present OR being added in
|
|
178
|
+
// this same batch of tuples under the same container.
|
|
179
|
+
const pendingKey = JSON.stringify(containerPath)
|
|
180
|
+
const pendingChildren = pendingChildrenByContainer.get(pendingKey) || new Set()
|
|
181
|
+
const eligible = new Set([...present, ...pendingChildren])
|
|
182
|
+
|
|
183
|
+
if (Array.isArray(codeKeys) && codeKeys.length) {
|
|
184
|
+
resolved = codeKeys.filter(k => eligible.has(k) && !EXCLUDE_KEYS.has(k))
|
|
185
|
+
}
|
|
186
|
+
if (Array.isArray(uses) && uses.length) {
|
|
187
|
+
for (let u = 0; u < uses.length; u++) {
|
|
188
|
+
const keyName = uses[u]
|
|
189
|
+
if (eligible.has(keyName) && !EXCLUDE_KEYS.has(keyName) && !resolved.includes(keyName)) {
|
|
190
|
+
resolved.push(keyName)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Ensure any pending children not referenced by code/uses still appear
|
|
195
|
+
// after code/uses-derived order, preserving stability.
|
|
196
|
+
if (pendingChildren.size) {
|
|
197
|
+
for (const child of pendingChildren) {
|
|
198
|
+
if (!EXCLUDE_KEYS.has(child) && !resolved.includes(child)) {
|
|
199
|
+
resolved.push(child)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (resolved.length) {
|
|
205
|
+
preferredOrderMap.set(JSON.stringify(containerPath), { path: containerPath, keys: resolved })
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 2) Include immediate parent paths from tuples (excluding schema paths)
|
|
210
|
+
const parents = getParentPathsFromTuples(tuples)
|
|
211
|
+
|
|
212
|
+
// 3) Build final orders: prefer schema-derived order when available, otherwise infer from state
|
|
213
|
+
const orders = []
|
|
214
|
+
const seen = new Set()
|
|
215
|
+
|
|
216
|
+
// Add preferred orders first
|
|
217
|
+
preferredOrderMap.forEach(v => {
|
|
218
|
+
const k = JSON.stringify(v.path)
|
|
219
|
+
if (!seen.has(k)) { seen.add(k); orders.push(v) }
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Add remaining parents with state-derived order
|
|
223
|
+
const fallbackOrders = computeOrdersFromState(root, parents)
|
|
224
|
+
for (let i = 0; i < fallbackOrders.length; i++) {
|
|
225
|
+
const v = fallbackOrders[i]
|
|
226
|
+
const k = JSON.stringify(v.path)
|
|
227
|
+
if (seen.has(k)) { continue }
|
|
228
|
+
// Merge in any pending children (for containers without schema edits)
|
|
229
|
+
const pending = pendingChildrenByContainer.get(k)
|
|
230
|
+
if (pending && pending.size) {
|
|
231
|
+
const existing = new Set(v.keys)
|
|
232
|
+
for (const child of pending) {
|
|
233
|
+
if (existing.has(child)) { continue }
|
|
234
|
+
v.keys.push(child)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
seen.add(k)
|
|
238
|
+
orders.push(v)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return orders
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
package/src/utils/services.js
CHANGED
|
@@ -243,5 +243,19 @@ export const SERVICE_METHODS = {
|
|
|
243
243
|
demoteFromAdmin: 'admin',
|
|
244
244
|
|
|
245
245
|
// Utility methods
|
|
246
|
-
getHealthStatus: 'core'
|
|
246
|
+
getHealthStatus: 'core',
|
|
247
|
+
|
|
248
|
+
// Screenshot methods
|
|
249
|
+
createScreenshotProject: 'screenshot',
|
|
250
|
+
getProjectScreenshots: 'screenshot',
|
|
251
|
+
reprocessProjectScreenshots: 'screenshot',
|
|
252
|
+
recreateProjectScreenshots: 'screenshot',
|
|
253
|
+
deleteProjectScreenshots: 'screenshot',
|
|
254
|
+
getThumbnailCandidate: 'screenshot',
|
|
255
|
+
updateProjectThumbnail: 'screenshot',
|
|
256
|
+
getPageScreenshot: 'screenshot',
|
|
257
|
+
getComponentScreenshot: 'screenshot',
|
|
258
|
+
getScreenshotByKey: 'screenshot',
|
|
259
|
+
getQueueStatistics: 'screenshot',
|
|
260
|
+
refreshThumbnail: 'screenshot'
|
|
247
261
|
}
|