@symbo.ls/sdk 3.1.2 → 3.2.6

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 (187) hide show
  1. package/README.md +143 -2
  2. package/dist/cjs/config/environment.js +98 -30
  3. package/dist/cjs/index.js +144 -24
  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 +743 -0
  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 +64 -16
  23. package/dist/cjs/state/RootStateManager.js +65 -0
  24. package/dist/cjs/state/rootEventBus.js +74 -0
  25. package/dist/cjs/utils/CollabClient.js +223 -0
  26. package/dist/cjs/utils/TokenManager.js +78 -30
  27. package/dist/cjs/utils/changePreprocessor.js +199 -0
  28. package/dist/cjs/utils/jsonDiff.js +145 -0
  29. package/dist/cjs/utils/ordering.js +309 -0
  30. package/dist/cjs/utils/services.js +301 -103
  31. package/dist/cjs/utils/validation.js +0 -3
  32. package/dist/esm/config/environment.js +98 -30
  33. package/dist/esm/index.js +49505 -8718
  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 +26895 -0
  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 +49062 -8569
  53. package/dist/esm/state/RootStateManager.js +90 -0
  54. package/dist/esm/state/rootEventBus.js +56 -0
  55. package/dist/esm/utils/CollabClient.js +18889 -0
  56. package/dist/esm/utils/TokenManager.js +78 -30
  57. package/dist/esm/utils/changePreprocessor.js +542 -0
  58. package/dist/esm/utils/jsonDiff.js +7011 -0
  59. package/dist/esm/utils/ordering.js +291 -0
  60. package/dist/esm/utils/services.js +301 -103
  61. package/dist/esm/utils/validation.js +116 -50
  62. package/dist/node/config/environment.js +98 -30
  63. package/dist/node/index.js +175 -32
  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 +724 -0
  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 +64 -16
  83. package/dist/node/state/RootStateManager.js +36 -0
  84. package/dist/node/state/rootEventBus.js +55 -0
  85. package/dist/node/utils/CollabClient.js +194 -0
  86. package/dist/node/utils/TokenManager.js +78 -30
  87. package/dist/node/utils/changePreprocessor.js +180 -0
  88. package/dist/node/utils/jsonDiff.js +116 -0
  89. package/dist/node/utils/ordering.js +290 -0
  90. package/dist/node/utils/services.js +301 -103
  91. package/dist/node/utils/validation.js +0 -3
  92. package/package.json +39 -21
  93. package/src/config/environment.js +99 -28
  94. package/src/index.js +181 -36
  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 +900 -0
  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 +80 -13
  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 +76 -0
  148. package/src/state/rootEventBus.js +67 -0
  149. package/src/utils/CollabClient.js +248 -0
  150. package/src/utils/TokenManager.js +88 -33
  151. package/src/utils/changePreprocessor.js +239 -0
  152. package/src/utils/jsonDiff.js +144 -0
  153. package/src/utils/ordering.js +271 -0
  154. package/src/utils/services.js +326 -107
  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 -1751
  159. package/dist/cjs/services/SocketIOService.js +0 -307
  160. package/dist/cjs/services/SocketService.js +0 -161
  161. package/dist/cjs/services/SymstoryService.js +0 -571
  162. package/dist/cjs/utils/basedQuerys.js +0 -181
  163. package/dist/cjs/utils/symstoryClient.js +0 -259
  164. package/dist/esm/services/AIService.js +0 -185
  165. package/dist/esm/services/BasedService.js +0 -5278
  166. package/dist/esm/services/CoreService.js +0 -2264
  167. package/dist/esm/services/SocketIOService.js +0 -470
  168. package/dist/esm/services/SocketService.js +0 -191
  169. package/dist/esm/services/SymstoryService.js +0 -7041
  170. package/dist/esm/utils/basedQuerys.js +0 -163
  171. package/dist/esm/utils/symstoryClient.js +0 -370
  172. package/dist/node/services/AIService.js +0 -136
  173. package/dist/node/services/BasedService.js +0 -1156
  174. package/dist/node/services/CoreService.js +0 -1722
  175. package/dist/node/services/SocketIOService.js +0 -278
  176. package/dist/node/services/SocketService.js +0 -142
  177. package/dist/node/services/SymstoryService.js +0 -542
  178. package/dist/node/utils/basedQuerys.js +0 -162
  179. package/dist/node/utils/symstoryClient.js +0 -230
  180. package/src/services/AIService.js +0 -150
  181. package/src/services/BasedService.js +0 -1301
  182. package/src/services/CoreService.js +0 -1943
  183. package/src/services/SocketIOService.js +0 -334
  184. package/src/services/SocketService.js +0 -168
  185. package/src/services/SymstoryService.js +0 -649
  186. package/src/utils/basedQuerys.js +0 -164
  187. package/src/utils/symstoryClient.js +0 -252
@@ -0,0 +1,144 @@
1
+ // Lightweight JSON diff & patch helpers for CollabClient
2
+ // Each op: { action: 'set' | 'del', path: [...string], value?: any }
3
+
4
+ // helper functions
5
+ function isPlainObject (o) {
6
+ return o && typeof o === 'object' && !Array.isArray(o)
7
+ }
8
+
9
+ function deepEqual (a, b) {
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 }
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
51
+ }
52
+
53
+ import * as Y from 'yjs'
54
+
55
+ // Retrieve the shared root map. We deliberately avoid creating a nested
56
+ // "root -> root" structure that previously caused an ever-growing tree.
57
+ function getRootMap (ydoc) {
58
+ // `getMap()` lazily initialises the map if it does not yet exist, so the
59
+ // returned instance is always defined.
60
+ return ydoc.getMap('root')
61
+ }
62
+
63
+ // diff algorithm
64
+ export function diffJson (prev, next, prefix = []) {
65
+ const ops = []
66
+ const _prefix = Array.isArray(prefix) ? prefix : []
67
+
68
+ // deletions
69
+ for (const key in prev) {
70
+ if (
71
+ Object.hasOwn(prev, key) &&
72
+ !(key in next)
73
+ ) {
74
+ ops.push({ action: 'del', path: [..._prefix, key] })
75
+ }
76
+ }
77
+
78
+ // additions / updates
79
+ for (const key in next) {
80
+ if (Object.hasOwn(next, key)) {
81
+ const pVal = prev?.[key]
82
+ const nVal = next[key]
83
+
84
+ if (isPlainObject(pVal) && isPlainObject(nVal)) {
85
+ ops.push(...diffJson(pVal, nVal, [..._prefix, key]))
86
+ } else if (!deepEqual(pVal, nVal)) {
87
+ ops.push({ action: 'set', path: [..._prefix, key], value: nVal })
88
+ }
89
+ }
90
+ }
91
+
92
+ return ops
93
+ }
94
+
95
+ // apply ops to Yjs
96
+ export function applyOpsToJson (ops, ydoc) {
97
+ if (!ydoc || !Array.isArray(ops) || !ops.length) { return }
98
+
99
+ // Wrap modifications in a transaction so that we can tag them with the
100
+ // special "remote" origin. This ensures that our local change listener
101
+ // (`afterTransaction`) can safely ignore these updates and prevents
102
+ // feedback loops where we would echo remote changes back to the server.
103
+ ydoc.transact(() => {
104
+ const root = getRootMap(ydoc)
105
+
106
+ ops.forEach(op => {
107
+ const { action, path = [], value } = op || {}
108
+ if (!path.length) { return }
109
+
110
+ let target = root
111
+
112
+ // Traverse (or lazily create) intermediate maps.
113
+ for (let i = 0; i < path.length - 1; i++) {
114
+ const key = path[i]
115
+ let next = target.get(key)
116
+
117
+ if (!(next instanceof Y.Map)) {
118
+ // If the key is missing or not a Y.Map, replace it with a new map so
119
+ // we have a consistent structure for nested updates.
120
+ const fresh = new Y.Map()
121
+
122
+ // Preserve any plain object that may have existed previously.
123
+ if (isPlainObject(next)) {
124
+ Object.entries(next).forEach(([k, v]) => fresh.set(k, v))
125
+ }
126
+
127
+ target.set(key, fresh)
128
+ next = fresh
129
+ }
130
+
131
+ target = next
132
+ }
133
+
134
+ const last = path[path.length - 1]
135
+
136
+ // Apply the leaf operation.
137
+ if (action === 'set') {
138
+ target.set(last, value)
139
+ } else if (action === 'del') {
140
+ target.delete(last)
141
+ }
142
+ })
143
+ }, 'remote')
144
+ }
@@ -0,0 +1,271 @@
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
+ // container object. This lets us include keys created in the same batch even
129
+ // if they are not yet present in the state object when we compute orders.
130
+ const pendingChildrenByContainer = new Map()
131
+ for (let i = 0; i < tuples.length; i++) {
132
+ const t = tuples[i]
133
+ if (!Array.isArray(t)) { continue }
134
+ const [action, path] = t
135
+ const p = normalizePath(path)
136
+ if (!Array.isArray(p) || p.length < 2) { continue }
137
+ // Ignore schema edits here – we want actual data container child keys
138
+ if (p[0] === 'schema') { continue }
139
+
140
+ // Treat the immediate parent as the container and the final segment as the
141
+ // child key, regardless of depth. This ensures nested containers such as
142
+ // ['components', 'Comp1', 'MainContent', 'TXButton'] correctly record
143
+ // 'TXButton' as a child of ['components', 'Comp1', 'MainContent'].
144
+ const containerPath = p.slice(0, -1)
145
+ const childKey = p[p.length - 1]
146
+ const key = JSON.stringify(containerPath)
147
+ if (!pendingChildrenByContainer.has(key)) {
148
+ pendingChildrenByContainer.set(key, new Set())
149
+ }
150
+ // We only track updates/sets; deletes need not appear in the desired order
151
+ if (action === 'update' || action === 'set') {
152
+ pendingChildrenByContainer.get(key).add(childKey)
153
+ }
154
+ }
155
+
156
+ // 1) Prefer code-derived order for corresponding data container when schema 'code' present
157
+ const preferredOrderMap = new Map()
158
+ for (let i = 0; i < tuples.length; i++) {
159
+ const t = tuples[i]
160
+ if (!Array.isArray(t)) {continue}
161
+ const [action, path, value] = t
162
+ const p = normalizePath(path)
163
+ if (action !== 'update' || !Array.isArray(p) || p.length < 3) {continue}
164
+ if (p[0] !== 'schema') {continue}
165
+ const [, type, key] = p
166
+ const containerPath = [type, key]
167
+ const uses = value && Array.isArray(value.uses) ? value.uses : null
168
+ const code = value && value.code
169
+
170
+ // Resolve present keys from state
171
+ const obj = (() => {
172
+ try { return root && typeof root.getByPath === 'function' ? root.getByPath(containerPath) : null } catch { return null }
173
+ })()
174
+ if (!obj) {continue}
175
+ const present = new Set(Object.keys(obj))
176
+ const EXCLUDE_KEYS = new Set(['__order'])
177
+
178
+ // Try to parse key order from schema.code
179
+ const codeKeys = extractTopLevelKeysFromCode(code)
180
+ let resolved = []
181
+ // Keys eligible for ordering are those already present OR being added in
182
+ // this same batch of tuples under the same container.
183
+ const pendingKey = JSON.stringify(containerPath)
184
+ const pendingChildren = pendingChildrenByContainer.get(pendingKey) || new Set()
185
+ const eligible = new Set([...present, ...pendingChildren])
186
+
187
+ if (Array.isArray(codeKeys) && codeKeys.length) {
188
+ resolved = codeKeys.filter(k => eligible.has(k) && !EXCLUDE_KEYS.has(k))
189
+ }
190
+ if (Array.isArray(uses) && uses.length) {
191
+ for (let u = 0; u < uses.length; u++) {
192
+ const keyName = uses[u]
193
+ if (eligible.has(keyName) && !EXCLUDE_KEYS.has(keyName) && !resolved.includes(keyName)) {
194
+ resolved.push(keyName)
195
+ }
196
+ }
197
+ }
198
+ // Ensure any pending children not referenced by code/uses still appear
199
+ // after code/uses-derived order, preserving stability.
200
+ if (pendingChildren.size) {
201
+ for (const child of pendingChildren) {
202
+ if (!EXCLUDE_KEYS.has(child) && !resolved.includes(child)) {
203
+ resolved.push(child)
204
+ }
205
+ }
206
+ }
207
+
208
+ if (resolved.length) {
209
+ preferredOrderMap.set(JSON.stringify(containerPath), { path: containerPath, keys: resolved })
210
+ }
211
+ }
212
+
213
+ // 2) Include immediate parent paths from tuples (excluding schema paths)
214
+ const parents = getParentPathsFromTuples(tuples)
215
+
216
+ // 3) Build final orders: prefer schema-derived order when available, otherwise infer from state
217
+ const orders = []
218
+ const seen = new Set()
219
+
220
+ // Add preferred orders first
221
+ preferredOrderMap.forEach(v => {
222
+ const k = JSON.stringify(v.path)
223
+ if (!seen.has(k)) { seen.add(k); orders.push(v) }
224
+ })
225
+
226
+ // Add remaining parents with state-derived order
227
+ const fallbackOrders = computeOrdersFromState(root, parents)
228
+ for (let i = 0; i < fallbackOrders.length; i++) {
229
+ const v = fallbackOrders[i]
230
+ const k = JSON.stringify(v.path)
231
+ if (seen.has(k)) { continue }
232
+ // Merge in any pending children (for containers without schema edits)
233
+ const pending = pendingChildrenByContainer.get(k)
234
+ if (pending && pending.size) {
235
+ const existingKeys = v.keys
236
+ const existingSet = new Set(existingKeys)
237
+
238
+ // Meta keys such as 'props' and 'text' should typically stay at the end
239
+ // of the order. When inserting brand-new children we try to place them
240
+ // before the first meta key so that structural children appear first.
241
+ const META_KEYS = new Set([
242
+ 'style', 'class', 'text', 'html', 'content', 'data', 'attr', 'state', 'scope',
243
+ 'define', 'on', 'extend', 'extends', 'childExtend', 'childExtends',
244
+ 'children', 'component', 'context', 'tag', 'key', '__order', 'if'
245
+ ])
246
+
247
+ let firstMetaIndex = existingKeys.length
248
+ for (let j = 0; j < existingKeys.length; j++) {
249
+ if (META_KEYS.has(existingKeys[j])) {
250
+ firstMetaIndex = j
251
+ break
252
+ }
253
+ }
254
+
255
+ for (const child of pending) {
256
+ if (existingSet.has(child)) { continue }
257
+ const insertIndex = firstMetaIndex
258
+ existingKeys.splice(insertIndex, 0, child)
259
+ existingSet.add(child)
260
+ // Keep subsequent new children grouped together in front of meta keys
261
+ firstMetaIndex++
262
+ }
263
+ }
264
+ seen.add(k)
265
+ orders.push(v)
266
+ }
267
+
268
+ return orders
269
+ }
270
+
271
+