primitive-admin 1.1.0-alpha.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/README.md +523 -109
  2. package/assets/skill/skills/primitive-platform/SKILL.md +289 -0
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +372 -21
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/admins.js +136 -30
  8. package/dist/src/commands/admins.js.map +1 -1
  9. package/dist/src/commands/analytics.d.ts +2 -0
  10. package/dist/src/commands/analytics.js +713 -55
  11. package/dist/src/commands/analytics.js.map +1 -1
  12. package/dist/src/commands/apps.d.ts +2 -0
  13. package/dist/src/commands/apps.js +65 -100
  14. package/dist/src/commands/apps.js.map +1 -1
  15. package/dist/src/commands/auth-sessions.d.ts +7 -0
  16. package/dist/src/commands/auth-sessions.js +144 -0
  17. package/dist/src/commands/auth-sessions.js.map +1 -0
  18. package/dist/src/commands/auth.d.ts +2 -0
  19. package/dist/src/commands/auth.js +238 -108
  20. package/dist/src/commands/auth.js.map +1 -1
  21. package/dist/src/commands/blob-buckets.d.ts +2 -0
  22. package/dist/src/commands/blob-buckets.js +331 -0
  23. package/dist/src/commands/blob-buckets.js.map +1 -0
  24. package/dist/src/commands/catalog.d.ts +2 -0
  25. package/dist/src/commands/catalog.js +63 -48
  26. package/dist/src/commands/catalog.js.map +1 -1
  27. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  28. package/dist/src/commands/collection-type-configs.js +85 -0
  29. package/dist/src/commands/collection-type-configs.js.map +1 -0
  30. package/dist/src/commands/collections.d.ts +2 -0
  31. package/dist/src/commands/collections.js +1282 -0
  32. package/dist/src/commands/collections.js.map +1 -0
  33. package/dist/src/commands/comparisons.d.ts +2 -0
  34. package/dist/src/commands/comparisons.js +6 -6
  35. package/dist/src/commands/comparisons.js.map +1 -1
  36. package/dist/src/commands/config.d.ts +46 -0
  37. package/dist/src/commands/config.js +465 -0
  38. package/dist/src/commands/config.js.map +1 -0
  39. package/dist/src/commands/connections.d.ts +2 -0
  40. package/dist/src/commands/connections.js +99 -0
  41. package/dist/src/commands/connections.js.map +1 -0
  42. package/dist/src/commands/cron-triggers.d.ts +2 -0
  43. package/dist/src/commands/cron-triggers.js +266 -0
  44. package/dist/src/commands/cron-triggers.js.map +1 -0
  45. package/dist/src/commands/database-type-configs.d.ts +2 -0
  46. package/dist/src/commands/database-type-configs.js +164 -0
  47. package/dist/src/commands/database-type-configs.js.map +1 -0
  48. package/dist/src/commands/database-types.d.ts +2 -0
  49. package/dist/src/commands/database-types.js +471 -0
  50. package/dist/src/commands/database-types.js.map +1 -0
  51. package/dist/src/commands/databases.d.ts +65 -0
  52. package/dist/src/commands/databases.js +1887 -243
  53. package/dist/src/commands/databases.js.map +1 -1
  54. package/dist/src/commands/documents.d.ts +60 -0
  55. package/dist/src/commands/documents.js +2888 -22
  56. package/dist/src/commands/documents.js.map +1 -1
  57. package/dist/src/commands/email-templates.d.ts +2 -0
  58. package/dist/src/commands/email-templates.js +175 -0
  59. package/dist/src/commands/email-templates.js.map +1 -0
  60. package/dist/src/commands/env.d.ts +23 -0
  61. package/dist/src/commands/env.js +395 -0
  62. package/dist/src/commands/env.js.map +1 -0
  63. package/dist/src/commands/feature-flags.d.ts +14 -0
  64. package/dist/src/commands/feature-flags.js +117 -0
  65. package/dist/src/commands/feature-flags.js.map +1 -0
  66. package/dist/src/commands/functions.d.ts +20 -0
  67. package/dist/src/commands/functions.js +1630 -0
  68. package/dist/src/commands/functions.js.map +1 -0
  69. package/dist/src/commands/group-type-configs.d.ts +2 -0
  70. package/dist/src/commands/group-type-configs.js +87 -0
  71. package/dist/src/commands/group-type-configs.js.map +1 -0
  72. package/dist/src/commands/groups.d.ts +2 -0
  73. package/dist/src/commands/groups.js +65 -113
  74. package/dist/src/commands/groups.js.map +1 -1
  75. package/dist/src/commands/guides.d.ts +223 -0
  76. package/dist/src/commands/guides.js +627 -69
  77. package/dist/src/commands/guides.js.map +1 -1
  78. package/dist/src/commands/init.d.ts +37 -0
  79. package/dist/src/commands/init.js +1693 -208
  80. package/dist/src/commands/init.js.map +1 -1
  81. package/dist/src/commands/integrations.d.ts +2 -0
  82. package/dist/src/commands/integrations.js +428 -187
  83. package/dist/src/commands/integrations.js.map +1 -1
  84. package/dist/src/commands/llm.d.ts +2 -0
  85. package/dist/src/commands/llm.js +4 -2
  86. package/dist/src/commands/llm.js.map +1 -1
  87. package/dist/src/commands/locks.d.ts +8 -0
  88. package/dist/src/commands/locks.js +175 -0
  89. package/dist/src/commands/locks.js.map +1 -0
  90. package/dist/src/commands/metadata-category-configs.d.ts +12 -0
  91. package/dist/src/commands/metadata-category-configs.js +113 -0
  92. package/dist/src/commands/metadata-category-configs.js.map +1 -0
  93. package/dist/src/commands/metadata.d.ts +2 -0
  94. package/dist/src/commands/metadata.js +288 -0
  95. package/dist/src/commands/metadata.js.map +1 -0
  96. package/dist/src/commands/prompts.d.ts +2 -0
  97. package/dist/src/commands/prompts.js +322 -634
  98. package/dist/src/commands/prompts.js.map +1 -1
  99. package/dist/src/commands/rule-sets.d.ts +3 -0
  100. package/dist/src/commands/rule-sets.js +139 -148
  101. package/dist/src/commands/rule-sets.js.map +1 -1
  102. package/dist/src/commands/scripts.d.ts +30 -0
  103. package/dist/src/commands/scripts.js +688 -0
  104. package/dist/src/commands/scripts.js.map +1 -0
  105. package/dist/src/commands/secrets.d.ts +2 -0
  106. package/dist/src/commands/secrets.js +109 -0
  107. package/dist/src/commands/secrets.js.map +1 -0
  108. package/dist/src/commands/sessions.d.ts +2 -0
  109. package/dist/src/commands/sessions.js +76 -0
  110. package/dist/src/commands/sessions.js.map +1 -0
  111. package/dist/src/commands/skill.d.ts +2 -0
  112. package/dist/src/commands/skill.js +29 -0
  113. package/dist/src/commands/skill.js.map +1 -0
  114. package/dist/src/commands/sync-app-settings.d.ts +158 -0
  115. package/dist/src/commands/sync-app-settings.js +328 -0
  116. package/dist/src/commands/sync-app-settings.js.map +1 -0
  117. package/dist/src/commands/sync.d.ts +2670 -0
  118. package/dist/src/commands/sync.js +17144 -834
  119. package/dist/src/commands/sync.js.map +1 -1
  120. package/dist/src/commands/tokens.d.ts +2 -0
  121. package/dist/src/commands/tokens.js +132 -22
  122. package/dist/src/commands/tokens.js.map +1 -1
  123. package/dist/src/commands/users.d.ts +2 -0
  124. package/dist/src/commands/users.js +542 -24
  125. package/dist/src/commands/users.js.map +1 -1
  126. package/dist/src/commands/vars.d.ts +8 -0
  127. package/dist/src/commands/vars.js +97 -0
  128. package/dist/src/commands/vars.js.map +1 -0
  129. package/dist/src/commands/waitlist.d.ts +2 -0
  130. package/dist/src/commands/waitlist.js +12 -11
  131. package/dist/src/commands/waitlist.js.map +1 -1
  132. package/dist/src/commands/webhooks.d.ts +31 -0
  133. package/dist/src/commands/webhooks.js +633 -0
  134. package/dist/src/commands/webhooks.js.map +1 -0
  135. package/dist/src/commands/workflows.d.ts +88 -0
  136. package/dist/src/commands/workflows.js +1568 -742
  137. package/dist/src/commands/workflows.js.map +1 -1
  138. package/dist/src/lib/access-rule-display.d.ts +21 -0
  139. package/dist/src/lib/access-rule-display.js +34 -0
  140. package/dist/src/lib/access-rule-display.js.map +1 -0
  141. package/dist/src/lib/api-client.d.ts +2550 -0
  142. package/dist/src/lib/api-client.js +2503 -160
  143. package/dist/src/lib/api-client.js.map +1 -1
  144. package/dist/src/lib/app-settings-descriptor.d.ts +263 -0
  145. package/dist/src/lib/app-settings-descriptor.js +583 -0
  146. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  147. package/dist/src/lib/auth-flow.d.ts +8 -0
  148. package/dist/src/lib/batch.d.ts +26 -0
  149. package/dist/src/lib/batch.js +32 -0
  150. package/dist/src/lib/batch.js.map +1 -0
  151. package/dist/src/lib/block-layout.d.ts +160 -0
  152. package/dist/src/lib/block-layout.js +451 -0
  153. package/dist/src/lib/block-layout.js.map +1 -0
  154. package/dist/src/lib/block-selector.d.ts +58 -0
  155. package/dist/src/lib/block-selector.js +92 -0
  156. package/dist/src/lib/block-selector.js.map +1 -0
  157. package/dist/src/lib/canonical-json.d.ts +12 -0
  158. package/dist/src/lib/canonical-json.js +35 -0
  159. package/dist/src/lib/canonical-json.js.map +1 -0
  160. package/dist/src/lib/channel.d.ts +30 -0
  161. package/dist/src/lib/channel.js +68 -0
  162. package/dist/src/lib/channel.js.map +1 -0
  163. package/dist/src/lib/cli-manifest.d.ts +68 -0
  164. package/dist/src/lib/cli-manifest.js +71 -0
  165. package/dist/src/lib/cli-manifest.js.map +1 -0
  166. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +101 -0
  167. package/dist/src/lib/codegen-shared/generatedFiles.js +191 -0
  168. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  169. package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
  170. package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
  171. package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
  172. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +38 -0
  173. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +46 -0
  174. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  175. package/dist/src/lib/collection-export.d.ts +184 -0
  176. package/dist/src/lib/collection-export.js +252 -0
  177. package/dist/src/lib/collection-export.js.map +1 -0
  178. package/dist/src/lib/config-json-field.d.ts +28 -0
  179. package/dist/src/lib/config-json-field.js +56 -0
  180. package/dist/src/lib/config-json-field.js.map +1 -0
  181. package/dist/src/lib/config-object-descriptor.d.ts +127 -0
  182. package/dist/src/lib/config-object-descriptor.js +740 -0
  183. package/dist/src/lib/config-object-descriptor.js.map +1 -0
  184. package/dist/src/lib/config-payload.d.ts +92 -0
  185. package/dist/src/lib/config-payload.js +161 -0
  186. package/dist/src/lib/config-payload.js.map +1 -0
  187. package/dist/src/lib/config-surface.d.ts +141 -0
  188. package/dist/src/lib/config-surface.js +368 -0
  189. package/dist/src/lib/config-surface.js.map +1 -0
  190. package/dist/src/lib/config-toml.d.ts +10 -0
  191. package/dist/src/lib/config-toml.js +42 -0
  192. package/dist/src/lib/config-toml.js.map +1 -0
  193. package/dist/src/lib/config.d.ts +71 -0
  194. package/dist/src/lib/config.js +71 -68
  195. package/dist/src/lib/config.js.map +1 -1
  196. package/dist/src/lib/confirm-prompt.d.ts +83 -0
  197. package/dist/src/lib/confirm-prompt.js +110 -0
  198. package/dist/src/lib/confirm-prompt.js.map +1 -0
  199. package/dist/src/lib/constants.d.ts +11 -0
  200. package/dist/src/lib/constants.js +12 -0
  201. package/dist/src/lib/constants.js.map +1 -0
  202. package/dist/src/lib/crash-handlers.d.ts +20 -0
  203. package/dist/src/lib/crash-handlers.js +49 -0
  204. package/dist/src/lib/crash-handlers.js.map +1 -0
  205. package/dist/src/lib/credentials-store.d.ts +104 -0
  206. package/dist/src/lib/credentials-store.js +336 -0
  207. package/dist/src/lib/credentials-store.js.map +1 -0
  208. package/dist/src/lib/csv.d.ts +47 -0
  209. package/dist/src/lib/csv.js +172 -0
  210. package/dist/src/lib/csv.js.map +1 -0
  211. package/dist/src/lib/data-input.d.ts +23 -0
  212. package/dist/src/lib/data-input.js +50 -0
  213. package/dist/src/lib/data-input.js.map +1 -0
  214. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  215. package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
  216. package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
  217. package/dist/src/lib/db-codegen/dbGenerator.d.ts +67 -0
  218. package/dist/src/lib/db-codegen/dbGenerator.js +170 -0
  219. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
  220. package/dist/src/lib/db-codegen/dbNaming.d.ts +87 -0
  221. package/dist/src/lib/db-codegen/dbNaming.js +180 -0
  222. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
  223. package/dist/src/lib/db-codegen/dbTemplates.d.ts +272 -0
  224. package/dist/src/lib/db-codegen/dbTemplates.js +480 -0
  225. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
  226. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +73 -0
  227. package/dist/src/lib/db-codegen/dbTsTypes.js +139 -0
  228. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
  229. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  230. package/dist/src/lib/db-codegen/dbTypeIR.js +525 -0
  231. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  232. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
  233. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
  234. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  235. package/dist/src/lib/deprecation.d.ts +22 -0
  236. package/dist/src/lib/deprecation.js +43 -0
  237. package/dist/src/lib/deprecation.js.map +1 -0
  238. package/dist/src/lib/document-export-permissions.d.ts +30 -0
  239. package/dist/src/lib/document-export-permissions.js +54 -0
  240. package/dist/src/lib/document-export-permissions.js.map +1 -0
  241. package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
  242. package/dist/src/lib/document-ingest-artifact.js +505 -0
  243. package/dist/src/lib/document-ingest-artifact.js.map +1 -0
  244. package/dist/src/lib/document-ingest-input.d.ts +51 -0
  245. package/dist/src/lib/document-ingest-input.js +132 -0
  246. package/dist/src/lib/document-ingest-input.js.map +1 -0
  247. package/dist/src/lib/document-ingest-rows.d.ts +137 -0
  248. package/dist/src/lib/document-ingest-rows.js +181 -0
  249. package/dist/src/lib/document-ingest-rows.js.map +1 -0
  250. package/dist/src/lib/document-ingest.d.ts +101 -0
  251. package/dist/src/lib/document-ingest.js +384 -0
  252. package/dist/src/lib/document-ingest.js.map +1 -0
  253. package/dist/src/lib/env-resolver-core.d.ts +258 -0
  254. package/dist/src/lib/env-resolver-core.js +447 -0
  255. package/dist/src/lib/env-resolver-core.js.map +1 -0
  256. package/dist/src/lib/env-resolver.d.ts +99 -0
  257. package/dist/src/lib/env-resolver.js +153 -0
  258. package/dist/src/lib/env-resolver.js.map +1 -0
  259. package/dist/src/lib/fetch.d.ts +5 -0
  260. package/dist/src/lib/function-bundle.d.ts +147 -0
  261. package/dist/src/lib/function-bundle.js +341 -0
  262. package/dist/src/lib/function-bundle.js.map +1 -0
  263. package/dist/src/lib/function-collect.d.ts +123 -0
  264. package/dist/src/lib/function-collect.js +610 -0
  265. package/dist/src/lib/function-collect.js.map +1 -0
  266. package/dist/src/lib/function-db-types.d.ts +202 -0
  267. package/dist/src/lib/function-db-types.js +870 -0
  268. package/dist/src/lib/function-db-types.js.map +1 -0
  269. package/dist/src/lib/function-document-types.d.ts +144 -0
  270. package/dist/src/lib/function-document-types.js +370 -0
  271. package/dist/src/lib/function-document-types.js.map +1 -0
  272. package/dist/src/lib/function-grants-preflight.d.ts +64 -0
  273. package/dist/src/lib/function-grants-preflight.js +105 -0
  274. package/dist/src/lib/function-grants-preflight.js.map +1 -0
  275. package/dist/src/lib/function-log-lines.d.ts +76 -0
  276. package/dist/src/lib/function-log-lines.js +160 -0
  277. package/dist/src/lib/function-log-lines.js.map +1 -0
  278. package/dist/src/lib/function-log-row.d.ts +29 -0
  279. package/dist/src/lib/function-log-row.js +73 -0
  280. package/dist/src/lib/function-log-row.js.map +1 -0
  281. package/dist/src/lib/function-log-tail.d.ts +132 -0
  282. package/dist/src/lib/function-log-tail.js +262 -0
  283. package/dist/src/lib/function-log-tail.js.map +1 -0
  284. package/dist/src/lib/function-run.d.ts +289 -0
  285. package/dist/src/lib/function-run.js +389 -0
  286. package/dist/src/lib/function-run.js.map +1 -0
  287. package/dist/src/lib/function-schema-codegen.d.ts +143 -0
  288. package/dist/src/lib/function-schema-codegen.js +420 -0
  289. package/dist/src/lib/function-schema-codegen.js.map +1 -0
  290. package/dist/src/lib/function-sync.d.ts +459 -0
  291. package/dist/src/lib/function-sync.js +1258 -0
  292. package/dist/src/lib/function-sync.js.map +1 -0
  293. package/dist/src/lib/function-trigger-listing.d.ts +27 -0
  294. package/dist/src/lib/function-trigger-listing.js +70 -0
  295. package/dist/src/lib/function-trigger-listing.js.map +1 -0
  296. package/dist/src/lib/function-typecheck.d.ts +86 -0
  297. package/dist/src/lib/function-typecheck.js +370 -0
  298. package/dist/src/lib/function-typecheck.js.map +1 -0
  299. package/dist/src/lib/function-versions.d.ts +122 -0
  300. package/dist/src/lib/function-versions.js +182 -0
  301. package/dist/src/lib/function-versions.js.map +1 -0
  302. package/dist/src/lib/generated-allowlist.d.ts +28 -0
  303. package/dist/src/lib/generated-allowlist.js +281 -0
  304. package/dist/src/lib/generated-allowlist.js.map +1 -0
  305. package/dist/src/lib/generated-config-surfaces.d.ts +2936 -0
  306. package/dist/src/lib/generated-config-surfaces.js +10569 -0
  307. package/dist/src/lib/generated-config-surfaces.js.map +1 -0
  308. package/dist/src/lib/generated-sdk-types.d.ts +12 -0
  309. package/dist/src/lib/generated-sdk-types.js +13 -0
  310. package/dist/src/lib/generated-sdk-types.js.map +1 -0
  311. package/dist/src/lib/generated-template-lint.d.ts +212 -0
  312. package/dist/src/lib/generated-template-lint.js +624 -0
  313. package/dist/src/lib/generated-template-lint.js.map +1 -0
  314. package/dist/src/lib/init-adopt.d.ts +16 -0
  315. package/dist/src/lib/init-adopt.js +34 -0
  316. package/dist/src/lib/init-adopt.js.map +1 -0
  317. package/dist/src/lib/init-assets.d.ts +39 -0
  318. package/dist/src/lib/init-assets.js +97 -0
  319. package/dist/src/lib/init-assets.js.map +1 -0
  320. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  321. package/dist/src/lib/init-client-platforms.js +71 -0
  322. package/dist/src/lib/init-client-platforms.js.map +1 -0
  323. package/dist/src/lib/init-config.d.ts +98 -0
  324. package/dist/src/lib/init-config.js +186 -0
  325. package/dist/src/lib/init-config.js.map +1 -0
  326. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  327. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  328. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  329. package/dist/src/lib/init-ios-links.d.ts +91 -0
  330. package/dist/src/lib/init-ios-links.js +219 -0
  331. package/dist/src/lib/init-ios-links.js.map +1 -0
  332. package/dist/src/lib/init-plan.d.ts +80 -0
  333. package/dist/src/lib/init-plan.js +95 -0
  334. package/dist/src/lib/init-plan.js.map +1 -0
  335. package/dist/src/lib/init-production-env.d.ts +48 -0
  336. package/dist/src/lib/init-production-env.js +59 -0
  337. package/dist/src/lib/init-production-env.js.map +1 -0
  338. package/dist/src/lib/init-schema.d.ts +74 -0
  339. package/dist/src/lib/init-schema.js +358 -0
  340. package/dist/src/lib/init-schema.js.map +1 -0
  341. package/dist/src/lib/init-xcode.d.ts +34 -0
  342. package/dist/src/lib/init-xcode.js +138 -0
  343. package/dist/src/lib/init-xcode.js.map +1 -0
  344. package/dist/src/lib/integration-request-config.d.ts +30 -0
  345. package/dist/src/lib/integration-request-config.js +145 -0
  346. package/dist/src/lib/integration-request-config.js.map +1 -0
  347. package/dist/src/lib/integration-selector.d.ts +42 -0
  348. package/dist/src/lib/integration-selector.js +46 -0
  349. package/dist/src/lib/integration-selector.js.map +1 -0
  350. package/dist/src/lib/ios-app-id.d.ts +34 -0
  351. package/dist/src/lib/ios-app-id.js +69 -0
  352. package/dist/src/lib/ios-app-id.js.map +1 -0
  353. package/dist/src/lib/list-options.d.ts +68 -0
  354. package/dist/src/lib/list-options.js +89 -0
  355. package/dist/src/lib/list-options.js.map +1 -0
  356. package/dist/src/lib/local-state.d.ts +55 -0
  357. package/dist/src/lib/local-state.js +167 -0
  358. package/dist/src/lib/local-state.js.map +1 -0
  359. package/dist/src/lib/local-test-cases.d.ts +63 -0
  360. package/dist/src/lib/local-test-cases.js +136 -0
  361. package/dist/src/lib/local-test-cases.js.map +1 -0
  362. package/dist/src/lib/log-inspection.d.ts +715 -0
  363. package/dist/src/lib/log-inspection.js +816 -0
  364. package/dist/src/lib/log-inspection.js.map +1 -0
  365. package/dist/src/lib/logout-admin-session.d.ts +33 -0
  366. package/dist/src/lib/logout-admin-session.js +70 -0
  367. package/dist/src/lib/logout-admin-session.js.map +1 -0
  368. package/dist/src/lib/migration-nag.d.ts +49 -0
  369. package/dist/src/lib/migration-nag.js +163 -0
  370. package/dist/src/lib/migration-nag.js.map +1 -0
  371. package/dist/src/lib/object-status-filter.d.ts +22 -0
  372. package/dist/src/lib/object-status-filter.js +45 -0
  373. package/dist/src/lib/object-status-filter.js.map +1 -0
  374. package/dist/src/lib/output.d.ts +124 -0
  375. package/dist/src/lib/output.js +219 -8
  376. package/dist/src/lib/output.js.map +1 -1
  377. package/dist/src/lib/package-manager.d.ts +140 -0
  378. package/dist/src/lib/package-manager.js +305 -0
  379. package/dist/src/lib/package-manager.js.map +1 -0
  380. package/dist/src/lib/paginate.d.ts +98 -0
  381. package/dist/src/lib/paginate.js +112 -0
  382. package/dist/src/lib/paginate.js.map +1 -0
  383. package/dist/src/lib/platform-owned.d.ts +63 -0
  384. package/dist/src/lib/platform-owned.js +85 -0
  385. package/dist/src/lib/platform-owned.js.map +1 -0
  386. package/dist/src/lib/project-config.d.ts +122 -0
  387. package/dist/src/lib/project-config.js +244 -0
  388. package/dist/src/lib/project-config.js.map +1 -0
  389. package/dist/src/lib/prompt-cost-format.d.ts +11 -0
  390. package/dist/src/lib/prompt-cost-format.js +41 -0
  391. package/dist/src/lib/prompt-cost-format.js.map +1 -0
  392. package/dist/src/lib/prompt-schema-codegen.d.ts +147 -0
  393. package/dist/src/lib/prompt-schema-codegen.js +462 -0
  394. package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
  395. package/dist/src/lib/query-operators.d.ts +43 -0
  396. package/dist/src/lib/query-operators.js +80 -0
  397. package/dist/src/lib/query-operators.js.map +1 -0
  398. package/dist/src/lib/record-filter.d.ts +18 -0
  399. package/dist/src/lib/record-filter.js +55 -0
  400. package/dist/src/lib/record-filter.js.map +1 -0
  401. package/dist/src/lib/refresh-admin-credentials.d.ts +73 -0
  402. package/dist/src/lib/refresh-admin-credentials.js +123 -0
  403. package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
  404. package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
  405. package/dist/src/lib/resolve-init-dev-port.js +55 -0
  406. package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
  407. package/dist/src/lib/resolve-init-server.d.ts +64 -0
  408. package/dist/src/lib/resolve-init-server.js +77 -0
  409. package/dist/src/lib/resolve-init-server.js.map +1 -0
  410. package/dist/src/lib/resolve-owner.d.ts +19 -0
  411. package/dist/src/lib/resolve-owner.js +20 -0
  412. package/dist/src/lib/resolve-owner.js.map +1 -0
  413. package/dist/src/lib/resolve-platform.d.ts +74 -0
  414. package/dist/src/lib/resolve-platform.js +105 -0
  415. package/dist/src/lib/resolve-platform.js.map +1 -0
  416. package/dist/src/lib/run-status.d.ts +19 -0
  417. package/dist/src/lib/run-status.generated.d.ts +39 -0
  418. package/dist/src/lib/run-status.generated.js +66 -0
  419. package/dist/src/lib/run-status.generated.js.map +1 -0
  420. package/dist/src/lib/run-status.js +19 -0
  421. package/dist/src/lib/run-status.js.map +1 -0
  422. package/dist/src/lib/server-text-normalization.d.ts +51 -0
  423. package/dist/src/lib/server-text-normalization.js +90 -0
  424. package/dist/src/lib/server-text-normalization.js.map +1 -0
  425. package/dist/src/lib/server-url.d.ts +22 -0
  426. package/dist/src/lib/server-url.js +33 -0
  427. package/dist/src/lib/server-url.js.map +1 -0
  428. package/dist/src/lib/signing-secret-status.d.ts +81 -0
  429. package/dist/src/lib/signing-secret-status.js +116 -0
  430. package/dist/src/lib/signing-secret-status.js.map +1 -0
  431. package/dist/src/lib/skill-installer.d.ts +71 -0
  432. package/dist/src/lib/skill-installer.js +441 -0
  433. package/dist/src/lib/skill-installer.js.map +1 -0
  434. package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
  435. package/dist/src/lib/snapshot-audit-source.js +58 -0
  436. package/dist/src/lib/snapshot-audit-source.js.map +1 -0
  437. package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
  438. package/dist/src/lib/snapshot-audit-store.js +196 -0
  439. package/dist/src/lib/snapshot-audit-store.js.map +1 -0
  440. package/dist/src/lib/snapshot-audit.d.ts +207 -0
  441. package/dist/src/lib/snapshot-audit.js +431 -0
  442. package/dist/src/lib/snapshot-audit.js.map +1 -0
  443. package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
  444. package/dist/src/lib/snapshot-build-rows.js +87 -0
  445. package/dist/src/lib/snapshot-build-rows.js.map +1 -0
  446. package/dist/src/lib/snapshot-build.d.ts +50 -0
  447. package/dist/src/lib/snapshot-build.js +111 -0
  448. package/dist/src/lib/snapshot-build.js.map +1 -0
  449. package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
  450. package/dist/src/lib/snapshot-manifest-layout.js +70 -0
  451. package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
  452. package/dist/src/lib/snapshots.d.ts +98 -0
  453. package/dist/src/lib/snapshots.js +294 -0
  454. package/dist/src/lib/snapshots.js.map +1 -0
  455. package/dist/src/lib/step-run-table.d.ts +43 -0
  456. package/dist/src/lib/step-run-table.js +129 -0
  457. package/dist/src/lib/step-run-table.js.map +1 -0
  458. package/dist/src/lib/storage-pending-retry.d.ts +26 -0
  459. package/dist/src/lib/storage-pending-retry.js +42 -0
  460. package/dist/src/lib/storage-pending-retry.js.map +1 -0
  461. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +42 -0
  462. package/dist/src/lib/swift-codegen/agentGenerator.js +118 -0
  463. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -0
  464. package/dist/src/lib/swift-codegen/banners.d.ts +24 -0
  465. package/dist/src/lib/swift-codegen/banners.js +25 -0
  466. package/dist/src/lib/swift-codegen/banners.js.map +1 -0
  467. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +113 -0
  468. package/dist/src/lib/swift-codegen/dbGenerator.js +926 -0
  469. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  470. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  471. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  472. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  473. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +139 -0
  474. package/dist/src/lib/swift-codegen/functionGenerator.js +462 -0
  475. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -0
  476. package/dist/src/lib/swift-codegen/generator.d.ts +100 -0
  477. package/dist/src/lib/swift-codegen/generator.js +457 -0
  478. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  479. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +87 -0
  480. package/dist/src/lib/swift-codegen/schemaToSwift.js +661 -0
  481. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  482. package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +94 -0
  483. package/dist/src/lib/swift-codegen/siblingSymbols.js +155 -0
  484. package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
  485. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  486. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  487. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  488. package/dist/src/lib/sync-dir-selector.d.ts +21 -0
  489. package/dist/src/lib/sync-dir-selector.js +30 -0
  490. package/dist/src/lib/sync-dir-selector.js.map +1 -0
  491. package/dist/src/lib/sync-paths.d.ts +128 -0
  492. package/dist/src/lib/sync-paths.js +195 -0
  493. package/dist/src/lib/sync-paths.js.map +1 -0
  494. package/dist/src/lib/sync-resource-types.d.ts +563 -0
  495. package/dist/src/lib/sync-resource-types.js +1073 -0
  496. package/dist/src/lib/sync-resource-types.js.map +1 -0
  497. package/dist/src/lib/sync-selectors.d.ts +138 -0
  498. package/dist/src/lib/sync-selectors.js +289 -0
  499. package/dist/src/lib/sync-selectors.js.map +1 -0
  500. package/dist/src/lib/template.d.ts +170 -0
  501. package/dist/src/lib/template.js +484 -68
  502. package/dist/src/lib/template.js.map +1 -1
  503. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  504. package/dist/src/lib/test-case-file-names.js +91 -0
  505. package/dist/src/lib/test-case-file-names.js.map +1 -0
  506. package/dist/src/lib/test-case-keys.d.ts +29 -0
  507. package/dist/src/lib/test-case-keys.js +55 -0
  508. package/dist/src/lib/test-case-keys.js.map +1 -0
  509. package/dist/src/lib/test-case-variables.d.ts +29 -0
  510. package/dist/src/lib/test-case-variables.js +71 -0
  511. package/dist/src/lib/test-case-variables.js.map +1 -0
  512. package/dist/src/lib/token-inject.d.ts +56 -0
  513. package/dist/src/lib/token-inject.js +204 -0
  514. package/dist/src/lib/token-inject.js.map +1 -0
  515. package/dist/src/lib/toml-database-config.d.ts +123 -0
  516. package/dist/src/lib/toml-database-config.js +544 -0
  517. package/dist/src/lib/toml-database-config.js.map +1 -0
  518. package/dist/src/lib/toml-metadata-config.d.ts +151 -0
  519. package/dist/src/lib/toml-metadata-config.js +476 -0
  520. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  521. package/dist/src/lib/toml-native-form.d.ts +46 -0
  522. package/dist/src/lib/toml-native-form.js +78 -0
  523. package/dist/src/lib/toml-native-form.js.map +1 -0
  524. package/dist/src/lib/toml-params-validator.d.ts +129 -0
  525. package/dist/src/lib/toml-params-validator.js +298 -0
  526. package/dist/src/lib/toml-params-validator.js.map +1 -0
  527. package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
  528. package/dist/src/lib/toml-scalar-edit.js +283 -0
  529. package/dist/src/lib/toml-scalar-edit.js.map +1 -0
  530. package/dist/src/lib/user-selector.d.ts +24 -0
  531. package/dist/src/lib/user-selector.js +33 -0
  532. package/dist/src/lib/user-selector.js.map +1 -0
  533. package/dist/src/lib/version-check.d.ts +35 -0
  534. package/dist/src/lib/version-check.js +241 -0
  535. package/dist/src/lib/version-check.js.map +1 -0
  536. package/dist/src/lib/watch.d.ts +121 -0
  537. package/dist/src/lib/watch.js +169 -0
  538. package/dist/src/lib/watch.js.map +1 -0
  539. package/dist/src/lib/web-url.d.ts +40 -0
  540. package/dist/src/lib/web-url.js +76 -0
  541. package/dist/src/lib/web-url.js.map +1 -0
  542. package/dist/src/lib/webhook-deliver.d.ts +209 -0
  543. package/dist/src/lib/webhook-deliver.js +519 -0
  544. package/dist/src/lib/webhook-deliver.js.map +1 -0
  545. package/dist/src/lib/workflow-apply.d.ts +110 -0
  546. package/dist/src/lib/workflow-apply.js +164 -0
  547. package/dist/src/lib/workflow-apply.js.map +1 -0
  548. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  549. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  550. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  551. package/dist/src/lib/workflow-codegen/generator.d.ts +96 -0
  552. package/dist/src/lib/workflow-codegen/generator.js +361 -0
  553. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  554. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +94 -0
  555. package/dist/src/lib/workflow-codegen/invokerIR.js +76 -0
  556. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  557. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  558. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  559. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  560. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +80 -0
  561. package/dist/src/lib/workflow-codegen/schemaToTs.js +303 -0
  562. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  563. package/dist/src/lib/workflow-config-apply.d.ts +70 -0
  564. package/dist/src/lib/workflow-config-apply.js +137 -0
  565. package/dist/src/lib/workflow-config-apply.js.map +1 -0
  566. package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
  567. package/dist/src/lib/workflow-config-sidecar.js +96 -0
  568. package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
  569. package/dist/src/lib/workflow-defaults.d.ts +29 -0
  570. package/dist/src/lib/workflow-defaults.js +41 -0
  571. package/dist/src/lib/workflow-defaults.js.map +1 -0
  572. package/dist/src/lib/workflow-fragments.d.ts +64 -0
  573. package/dist/src/lib/workflow-fragments.js +342 -0
  574. package/dist/src/lib/workflow-fragments.js.map +1 -0
  575. package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
  576. package/dist/src/lib/workflow-include-preserve.js +286 -0
  577. package/dist/src/lib/workflow-include-preserve.js.map +1 -0
  578. package/dist/src/lib/workflow-payload.d.ts +98 -0
  579. package/dist/src/lib/workflow-payload.js +178 -0
  580. package/dist/src/lib/workflow-payload.js.map +1 -0
  581. package/dist/src/lib/workflow-toml-validator.d.ts +211 -0
  582. package/dist/src/lib/workflow-toml-validator.js +770 -0
  583. package/dist/src/lib/workflow-toml-validator.js.map +1 -0
  584. package/dist/src/lib/workflow-usage.d.ts +196 -0
  585. package/dist/src/lib/workflow-usage.js +310 -0
  586. package/dist/src/lib/workflow-usage.js.map +1 -0
  587. package/dist/src/types/index.d.ts +591 -0
  588. package/dist/src/validators.d.ts +65 -0
  589. package/dist/src/validators.js +64 -0
  590. package/dist/src/validators.js.map +1 -0
  591. package/package.json +34 -9
@@ -0,0 +1,1282 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ApiClient } from "../lib/api-client.js";
4
+ import { resolveAppId } from "../lib/config.js";
5
+ import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, json, flushOutput, } from "../lib/output.js";
6
+ import { confirmPrompt } from "../lib/confirm-prompt.js";
7
+ import { pageCursorOption, pageLimitOption, parsePageLimit, printEmptyPage, printPageHint, } from "../lib/list-options.js";
8
+ import { normalizeCliListEnvelope, wholeListEnvelope, } from "../lib/paginate.js";
9
+ import { resolveOwnerUserId } from "../lib/resolve-owner.js";
10
+ import { COLLECTION_EXPORT_FILENAME, COLLECTION_EXPORT_VERSION, duplicateNameSources, emptyImportSummary, grantDisposition, grantUpdateLine, importExitCode, planCollection, readCollectionExportFile, serverReason, } from "../lib/collection-export.js";
11
+ export function registerCollectionsCommands(program) {
12
+ const collections = program
13
+ .command("collections")
14
+ .description("Create and manage document collections and their access")
15
+ .addHelpText("after", `
16
+ Examples:
17
+ $ primitive collections list
18
+ $ primitive collections create "Sprint 12"
19
+ $ primitive collections create "Sprint 12" --owner user@example.com
20
+ $ primitive collections get <collection-id>
21
+ $ primitive collections documents list <collection-id>
22
+ $ primitive collections documents add <collection-id> <document-id>
23
+ $ primitive collections access <collection-id>
24
+ $ primitive collections share <collection-id> --group team/engineering --permission read-write
25
+ $ primitive collections members list <collection-id>
26
+ $ primitive collections members add <collection-id> <user-id> --permission reader
27
+ $ primitive collections for-document <document-id>
28
+ `);
29
+ // List collections
30
+ collections
31
+ .command("list")
32
+ .description("List collections in an app. By default shows only collections " +
33
+ "you're a direct member of. Use --all to see every collection (admin only).")
34
+ .argument("[app-id]", "App ID (uses current app if not specified)")
35
+ .option("--app <app-id>", "App ID")
36
+ .option("--all", "List every collection in the app (admin-only). Default is collections you're a direct member of.")
37
+ .addOption(pageLimitOption())
38
+ .addOption(pageCursorOption())
39
+ .option("--json", "Output as JSON")
40
+ .action(async (appId, options) => {
41
+ const limit = parsePageLimit(options.limit);
42
+ const resolvedAppId = resolveAppId(appId, options);
43
+ const client = new ApiClient();
44
+ try {
45
+ const params = { limit, cursor: options.cursor };
46
+ const page = normalizeCliListEnvelope(options.all
47
+ ? await client.listAllCollections(resolvedAppId, params)
48
+ : await client.listCollections(resolvedAppId, params));
49
+ const list = page.items;
50
+ if (options.json) {
51
+ json(page);
52
+ return;
53
+ }
54
+ if (list.length === 0) {
55
+ printEmptyPage(page, "collections");
56
+ return;
57
+ }
58
+ const columns = [
59
+ { header: "ID", key: "collectionId", format: formatId },
60
+ { header: "NAME", key: "name" },
61
+ { header: "DESCRIPTION", key: "description", format: (v) => v || "—" },
62
+ { header: "DOCS", key: "documentCount" },
63
+ { header: "CREATED", key: "createdAt", format: formatDate },
64
+ ];
65
+ // `permission` is only returned by the default list() endpoint
66
+ if (!options.all) {
67
+ columns.splice(4, 0, { header: "PERMISSION", key: "permission", format: (v) => v || "—" });
68
+ }
69
+ console.log(formatTable(list, columns));
70
+ printPageHint(page);
71
+ }
72
+ catch (err) {
73
+ // Friendly message when --all is used by a non-admin
74
+ if (options.all && /403|forbidden|insufficient permissions/i.test(err.message || "")) {
75
+ error("--all requires admin role on this app. Omit the flag to see collections you're a member of.");
76
+ process.exit(1);
77
+ }
78
+ error(err.message);
79
+ process.exit(1);
80
+ }
81
+ });
82
+ // Create collection
83
+ collections
84
+ .command("create")
85
+ .description("Create a new collection")
86
+ .argument("<name>", "Collection name")
87
+ .option("--description <description>", "Collection description")
88
+ .option("--initial-metadata <json>", "Create-time resource metadata as JSON, keyed by category name (issue #1420)")
89
+ .option("--app <app-id>", "App ID")
90
+ .option("--owner <userId-or-email>", "User ID or email to own the collection (super-admin or assigned-console-admin tokens only)")
91
+ .option("--json", "Output as JSON")
92
+ .addHelpText("after", `
93
+ Ownership:
94
+ An app-user token — member, admin, or owner — always creates the collection
95
+ owned by the caller: the server ignores --owner for those tokens.
96
+ A super-admin token, or a console-admin token assigned to this app, acts
97
+ through an admin shadow app user — without --owner that shadow user owns the
98
+ collection, with --owner the named user does.
99
+ A console admin not assigned to this app has no access to the app at all.
100
+
101
+ The owner is the collection's creator, so under the default rules they — and
102
+ no other non-admin — can edit it, delete it, and manage its documents and
103
+ members.
104
+
105
+ An email given to --owner is resolved to a user id before anything is
106
+ created; a user who is not in the app fails the command without creating a
107
+ collection.
108
+ `)
109
+ .action(async (name, options) => {
110
+ const resolvedAppId = resolveAppId(undefined, options);
111
+ const client = new ApiClient();
112
+ // Resolve --owner to a userId through the one shared lookup (#2763),
113
+ // before the create: an email nobody answers to must fail with nothing
114
+ // created, not leave a collection owned by the admin.
115
+ let ownerUserId;
116
+ if (options.owner) {
117
+ try {
118
+ ownerUserId = await resolveOwnerUserId(client, resolvedAppId, options.owner);
119
+ }
120
+ catch (err) {
121
+ error(err.message);
122
+ process.exit(1);
123
+ }
124
+ }
125
+ try {
126
+ const data = { name };
127
+ if (ownerUserId)
128
+ data.createdBy = ownerUserId;
129
+ if (options.description)
130
+ data.description = options.description;
131
+ // Create-time per-category ResourceMetadata rows (issue #1420).
132
+ if (options.initialMetadata) {
133
+ try {
134
+ data.initialMetadata = JSON.parse(options.initialMetadata);
135
+ }
136
+ catch {
137
+ error("Invalid --initial-metadata JSON.");
138
+ process.exit(1);
139
+ }
140
+ }
141
+ const result = await client.createCollection(resolvedAppId, data);
142
+ if (options.json) {
143
+ json(result);
144
+ return;
145
+ }
146
+ success("Collection created.");
147
+ keyValue("Collection ID", result.collectionId);
148
+ keyValue("Name", result.name);
149
+ // Read back from the RESPONSE, not from --owner: whom the server
150
+ // credited is the answer, and for a non-admin token it is the caller.
151
+ keyValue("Owner", result.createdBy);
152
+ if (result.description)
153
+ keyValue("Description", result.description);
154
+ }
155
+ catch (err) {
156
+ error(err.message);
157
+ process.exit(1);
158
+ }
159
+ });
160
+ // Get collection
161
+ collections
162
+ .command("get")
163
+ .description("Get collection details")
164
+ .argument("<collection-id>", "Collection ID")
165
+ .option("--app <app-id>", "App ID")
166
+ .option("--json", "Output as JSON")
167
+ .action(async (collectionId, options) => {
168
+ const resolvedAppId = resolveAppId(undefined, options);
169
+ const client = new ApiClient();
170
+ try {
171
+ const result = await client.getCollection(resolvedAppId, collectionId);
172
+ if (options.json) {
173
+ json(result);
174
+ return;
175
+ }
176
+ printResult("Collection ID", result.collectionId);
177
+ printResult("Name", result.name);
178
+ printResult("Description", result.description || "—");
179
+ printResult("Documents", result.documentCount);
180
+ printResult("Created", formatDate(result.createdAt));
181
+ }
182
+ catch (err) {
183
+ error(err.message);
184
+ process.exit(1);
185
+ }
186
+ });
187
+ // Update collection
188
+ collections
189
+ .command("update")
190
+ .description("Update a collection")
191
+ .argument("<collection-id>", "Collection ID")
192
+ .option("--name <name>", "New collection name")
193
+ .option("--description <description>", "New collection description")
194
+ .option("--app <app-id>", "App ID")
195
+ .option("--json", "Output as JSON")
196
+ .action(async (collectionId, options) => {
197
+ const resolvedAppId = resolveAppId(undefined, options);
198
+ const client = new ApiClient();
199
+ const data = {};
200
+ if (options.name)
201
+ data.name = options.name;
202
+ if (options.description)
203
+ data.description = options.description;
204
+ if (Object.keys(data).length === 0) {
205
+ error("Provide at least --name or --description to update.");
206
+ process.exit(1);
207
+ }
208
+ try {
209
+ const result = await client.updateCollection(resolvedAppId, collectionId, data);
210
+ if (options.json) {
211
+ json(result);
212
+ return;
213
+ }
214
+ success(`Collection ${collectionId} updated.`);
215
+ }
216
+ catch (err) {
217
+ error(err.message);
218
+ process.exit(1);
219
+ }
220
+ });
221
+ // Delete collection
222
+ collections
223
+ .command("delete")
224
+ .description("Delete a collection (documents are preserved)")
225
+ .argument("<collection-id>", "Collection ID")
226
+ .option("--app <app-id>", "App ID")
227
+ .option("-y, --yes", "Skip confirmation prompt")
228
+ .option("--json", "Output as JSON")
229
+ .action(async (collectionId, options) => {
230
+ const resolvedAppId = resolveAppId(undefined, options);
231
+ if (!options.yes) {
232
+ let confirm;
233
+ try {
234
+ confirm = await confirmPrompt(`Delete collection ${collectionId}? This revokes all collection-granted permissions but does NOT delete documents.`);
235
+ }
236
+ catch (err) {
237
+ error(err.message);
238
+ process.exit(1);
239
+ }
240
+ if (!confirm) {
241
+ info("Cancelled.");
242
+ return;
243
+ }
244
+ }
245
+ const client = new ApiClient();
246
+ try {
247
+ await client.deleteCollection(resolvedAppId, collectionId);
248
+ // What a script gets to confirm the removal (#3647). The server's body
249
+ // here is a bare `{ success: true }` that names nothing, so the ids are
250
+ // named by the CLI, as `documents transfer-owner` does.
251
+ if (options.json) {
252
+ json({ success: true, collectionId });
253
+ return;
254
+ }
255
+ success(`Collection ${collectionId} deleted.`);
256
+ }
257
+ catch (err) {
258
+ error(err.message);
259
+ process.exit(1);
260
+ }
261
+ });
262
+ // ---- Access command ----
263
+ collections
264
+ .command("access")
265
+ .description("Combined view of the groups and members with access to a collection " +
266
+ "(members only: 'collections members list'; group grants: 'collections share'/'unshare')")
267
+ .argument("<collection-id>", "Collection ID")
268
+ .option("--app <app-id>", "App ID")
269
+ .option("--json", "Output as JSON")
270
+ .action(async (collectionId, options) => {
271
+ const resolvedAppId = resolveAppId(undefined, options);
272
+ const client = new ApiClient();
273
+ try {
274
+ const result = await client.getCollectionAccess(resolvedAppId, collectionId);
275
+ if (options.json) {
276
+ json(result);
277
+ return;
278
+ }
279
+ const groups = result.groups || [];
280
+ const members = result.members || [];
281
+ if (groups.length === 0 && members.length === 0) {
282
+ info("No groups or members have access to this collection.");
283
+ return;
284
+ }
285
+ if (groups.length > 0) {
286
+ console.log("\nGroups:");
287
+ console.log(formatTable(groups, [
288
+ { header: "TYPE", key: "groupType" },
289
+ { header: "ID", key: "groupId" },
290
+ { header: "PERMISSION", key: "permission" },
291
+ { header: "GRANTED", key: "grantedAt", format: formatDate },
292
+ ]));
293
+ }
294
+ if (members.length > 0) {
295
+ console.log("\nMembers:");
296
+ console.log(formatTable(members, [
297
+ { header: "USER_ID", key: "userId", format: formatId },
298
+ { header: "PERMISSION", key: "permission" },
299
+ ]));
300
+ }
301
+ }
302
+ catch (err) {
303
+ error(err.message);
304
+ process.exit(1);
305
+ }
306
+ });
307
+ // ---- Share / unshare (group permissions) ----
308
+ collections
309
+ .command("share")
310
+ .description("Grant a group access to a collection")
311
+ .argument("<collection-id>", "Collection ID")
312
+ .requiredOption("--group <group>", "Group in format type/id (e.g. team/engineering)")
313
+ .requiredOption("--permission <permission>", "Permission level (reader or read-write)")
314
+ .option("--app <app-id>", "App ID")
315
+ .option("--json", "Output as JSON")
316
+ .action(async (collectionId, options) => {
317
+ const resolvedAppId = resolveAppId(undefined, options);
318
+ const client = new ApiClient();
319
+ const parts = options.group.split("/");
320
+ if (parts.length !== 2) {
321
+ error("Group must be in format type/id (e.g. team/engineering).");
322
+ process.exit(1);
323
+ }
324
+ const [groupType, groupId] = parts;
325
+ try {
326
+ const result = await client.grantCollectionGroupPermission(resolvedAppId, collectionId, {
327
+ groupType,
328
+ groupId,
329
+ permission: options.permission,
330
+ });
331
+ if (options.json) {
332
+ json(result);
333
+ return;
334
+ }
335
+ success(`Granted ${options.permission} access to ${groupType}/${groupId} on collection.`);
336
+ }
337
+ catch (err) {
338
+ error(err.message);
339
+ process.exit(1);
340
+ }
341
+ });
342
+ collections
343
+ .command("unshare")
344
+ .description("Revoke a group's access to a collection")
345
+ .argument("<collection-id>", "Collection ID")
346
+ .requiredOption("--group <group>", "Group in format type/id (e.g. team/engineering)")
347
+ .option("--app <app-id>", "App ID")
348
+ .option("-y, --yes", "Skip confirmation prompt")
349
+ .option("--json", "Output as JSON")
350
+ .action(async (collectionId, options) => {
351
+ const resolvedAppId = resolveAppId(undefined, options);
352
+ const parts = options.group.split("/");
353
+ if (parts.length !== 2) {
354
+ error("Group must be in format type/id (e.g. team/engineering).");
355
+ process.exit(1);
356
+ }
357
+ const [groupType, groupId] = parts;
358
+ if (!options.yes) {
359
+ let confirm;
360
+ try {
361
+ confirm = await confirmPrompt(`Revoke ${groupType}/${groupId} access to collection ${collectionId}?`);
362
+ }
363
+ catch (err) {
364
+ error(err.message);
365
+ process.exit(1);
366
+ }
367
+ if (!confirm) {
368
+ info("Cancelled.");
369
+ return;
370
+ }
371
+ }
372
+ const client = new ApiClient();
373
+ try {
374
+ await client.revokeCollectionGroupPermission(resolvedAppId, collectionId, groupType, groupId);
375
+ if (options.json) {
376
+ json({ success: true, collectionId, groupType, groupId });
377
+ return;
378
+ }
379
+ success(`Revoked ${groupType}/${groupId} access from collection.`);
380
+ }
381
+ catch (err) {
382
+ error(err.message);
383
+ process.exit(1);
384
+ }
385
+ });
386
+ // ---- Documents subcommand group ----
387
+ const documents = collections
388
+ .command("documents")
389
+ .description("Manage documents in a collection");
390
+ // List documents
391
+ documents
392
+ .command("list")
393
+ .description("List documents in a collection")
394
+ .argument("<collection-id>", "Collection ID")
395
+ .option("--app <app-id>", "App ID")
396
+ .addOption(pageLimitOption())
397
+ .addOption(pageCursorOption())
398
+ .option("--json", "Output as JSON")
399
+ .action(async (collectionId, options) => {
400
+ const limit = parsePageLimit(options.limit);
401
+ const resolvedAppId = resolveAppId(undefined, options);
402
+ const client = new ApiClient();
403
+ try {
404
+ const page = normalizeCliListEnvelope(await client.listCollectionDocuments(resolvedAppId, collectionId, {
405
+ limit,
406
+ cursor: options.cursor,
407
+ }));
408
+ const list = page.items;
409
+ if (options.json) {
410
+ json(page);
411
+ return;
412
+ }
413
+ if (list.length === 0) {
414
+ printEmptyPage(page, "documents", "No documents in this collection.");
415
+ return;
416
+ }
417
+ console.log(formatTable(list, [
418
+ { header: "DOCUMENT_ID", key: "documentId", format: formatId },
419
+ { header: "ADDED", key: "addedAt", format: formatDate },
420
+ ]));
421
+ printPageHint(page);
422
+ }
423
+ catch (err) {
424
+ error(err.message);
425
+ process.exit(1);
426
+ }
427
+ });
428
+ // Add document
429
+ documents
430
+ .command("add")
431
+ .description("Add a document to a collection")
432
+ .argument("<collection-id>", "Collection ID")
433
+ .argument("<document-id>", "Document ID")
434
+ .option("--app <app-id>", "App ID")
435
+ .option("--json", "Output as JSON")
436
+ .action(async (collectionId, documentId, options) => {
437
+ const resolvedAppId = resolveAppId(undefined, options);
438
+ const client = new ApiClient();
439
+ try {
440
+ const result = await client.addCollectionDocument(resolvedAppId, collectionId, {
441
+ documentId,
442
+ });
443
+ if (options.json) {
444
+ json(result);
445
+ return;
446
+ }
447
+ success(`Document ${documentId} added to collection.`);
448
+ }
449
+ catch (err) {
450
+ error(err.message);
451
+ process.exit(1);
452
+ }
453
+ });
454
+ // Remove document
455
+ documents
456
+ .command("remove")
457
+ .description("Remove a document from a collection")
458
+ .argument("<collection-id>", "Collection ID")
459
+ .argument("<document-id>", "Document ID")
460
+ .option("--app <app-id>", "App ID")
461
+ .option("-y, --yes", "Skip confirmation prompt")
462
+ .option("--json", "Output as JSON")
463
+ .action(async (collectionId, documentId, options) => {
464
+ const resolvedAppId = resolveAppId(undefined, options);
465
+ if (!options.yes) {
466
+ let confirm;
467
+ try {
468
+ confirm = await confirmPrompt(`Remove document ${documentId} from collection? Collection-sourced permissions will be revoked.`);
469
+ }
470
+ catch (err) {
471
+ error(err.message);
472
+ process.exit(1);
473
+ }
474
+ if (!confirm) {
475
+ info("Cancelled.");
476
+ return;
477
+ }
478
+ }
479
+ const client = new ApiClient();
480
+ try {
481
+ await client.removeCollectionDocument(resolvedAppId, collectionId, documentId);
482
+ if (options.json) {
483
+ json({ success: true, collectionId, documentId });
484
+ return;
485
+ }
486
+ success(`Document ${documentId} removed from collection.`);
487
+ }
488
+ catch (err) {
489
+ error(err.message);
490
+ process.exit(1);
491
+ }
492
+ });
493
+ // ---- List collections for a document (reverse lookup) ----
494
+ collections
495
+ .command("for-document")
496
+ .description("List collections that a document belongs to")
497
+ .argument("<document-id>", "Document ID")
498
+ .option("--app <app-id>", "App ID")
499
+ .option("--json", "Output as JSON")
500
+ .action(async (documentId, options) => {
501
+ const resolvedAppId = resolveAppId(undefined, options);
502
+ const client = new ApiClient();
503
+ try {
504
+ const result = await client.listCollectionsForDocument(resolvedAppId, documentId);
505
+ const list = Array.isArray(result) ? result : result?.items ?? [];
506
+ if (options.json) {
507
+ json(result);
508
+ return;
509
+ }
510
+ if (list.length === 0) {
511
+ info("Document is not in any collections.");
512
+ return;
513
+ }
514
+ console.log(formatTable(list, [
515
+ { header: "COLLECTION_ID", key: "collectionId", format: formatId },
516
+ { header: "NAME", key: "name" },
517
+ { header: "ADDED", key: "addedAt", format: formatDate },
518
+ ]));
519
+ if (result?.nextCursor) {
520
+ info(`More results available. Use --json to get the cursor for pagination.`);
521
+ }
522
+ }
523
+ catch (err) {
524
+ error(err.message);
525
+ process.exit(1);
526
+ }
527
+ });
528
+ // ---- Members subcommand group ----
529
+ const members = collections
530
+ .command("members")
531
+ .description("Manage individual user access to a collection");
532
+ // List members
533
+ //
534
+ // The server has no GET /collections/:id/members route — membership is read
535
+ // from the access endpoint, which returns groups and members together. This
536
+ // command is the members half of it, so reading "who is on this collection"
537
+ // doesn't require knowing about a command named for neither (#2764).
538
+ members
539
+ .command("list")
540
+ .description("List users with direct access to a collection")
541
+ .argument("<collection-id>", "Collection ID")
542
+ .option("--app <app-id>", "App ID")
543
+ .option("--json", "Output as JSON")
544
+ .action(async (collectionId, options) => {
545
+ const resolvedAppId = resolveAppId(undefined, options);
546
+ const client = new ApiClient();
547
+ try {
548
+ const result = await client.getCollectionAccess(resolvedAppId, collectionId);
549
+ const list = result.members ?? [];
550
+ if (options.json) {
551
+ // The members half of the access payload, in the one list envelope
552
+ // (#3646) — the same shape `groups members list` prints (#2764).
553
+ json(wholeListEnvelope(list));
554
+ return;
555
+ }
556
+ if (list.length === 0) {
557
+ info("No members have direct access to this collection. Groups may still have access — see 'collections access'.");
558
+ return;
559
+ }
560
+ console.log(formatTable(list, [
561
+ { header: "USER_ID", key: "userId", format: formatId },
562
+ { header: "PERMISSION", key: "permission" },
563
+ ]));
564
+ }
565
+ catch (err) {
566
+ error(err.message);
567
+ process.exit(1);
568
+ }
569
+ });
570
+ // Add member
571
+ members
572
+ .command("add")
573
+ .description("Add a user to a collection")
574
+ .argument("<collection-id>", "Collection ID")
575
+ .argument("<user-id>", "User ID")
576
+ .requiredOption("--permission <permission>", "Permission level (reader or read-write)")
577
+ .option("--app <app-id>", "App ID")
578
+ .option("--json", "Output as JSON")
579
+ .action(async (collectionId, userId, options) => {
580
+ const resolvedAppId = resolveAppId(undefined, options);
581
+ const client = new ApiClient();
582
+ try {
583
+ const result = await client.addCollectionMember(resolvedAppId, collectionId, {
584
+ userId,
585
+ permission: options.permission,
586
+ });
587
+ if (options.json) {
588
+ json(result);
589
+ return;
590
+ }
591
+ success(`User ${userId} added to collection with ${options.permission} access.`);
592
+ }
593
+ catch (err) {
594
+ error(err.message);
595
+ process.exit(1);
596
+ }
597
+ });
598
+ // Remove member
599
+ members
600
+ .command("remove")
601
+ .description("Remove a user from a collection")
602
+ .argument("<collection-id>", "Collection ID")
603
+ .argument("<user-id>", "User ID")
604
+ .option("--app <app-id>", "App ID")
605
+ .option("-y, --yes", "Skip confirmation prompt")
606
+ .option("--json", "Output as JSON")
607
+ .action(async (collectionId, userId, options) => {
608
+ const resolvedAppId = resolveAppId(undefined, options);
609
+ if (!options.yes) {
610
+ let confirm;
611
+ try {
612
+ confirm = await confirmPrompt(`Remove user ${userId} from collection ${collectionId}?`);
613
+ }
614
+ catch (err) {
615
+ error(err.message);
616
+ process.exit(1);
617
+ }
618
+ if (!confirm) {
619
+ info("Cancelled.");
620
+ return;
621
+ }
622
+ }
623
+ const client = new ApiClient();
624
+ try {
625
+ await client.removeCollectionMember(resolvedAppId, collectionId, userId);
626
+ if (options.json) {
627
+ json({ success: true, collectionId, userId });
628
+ return;
629
+ }
630
+ success(`User ${userId} removed from collection.`);
631
+ }
632
+ catch (err) {
633
+ error(err.message);
634
+ process.exit(1);
635
+ }
636
+ });
637
+ registerCollectionsExport(collections);
638
+ registerCollectionsImport(collections);
639
+ }
640
+ // ---------------------------------------------------------------------------
641
+ // Export (#3643)
642
+ // ---------------------------------------------------------------------------
643
+ /**
644
+ * `primitive collections export` — the collections half of a migration.
645
+ *
646
+ * `documents export-all` writes `manifest.json` and a directory per document;
647
+ * this writes `collections.json` beside them, so one directory carries the
648
+ * whole app. Owner and member identities travel as EMAILS wherever the server
649
+ * can resolve one: users are app-scoped (`models.yaml`), so the same person is
650
+ * a different userId in every app and a recorded source userId means nothing
651
+ * in the target.
652
+ */
653
+ function registerCollectionsExport(collections) {
654
+ collections
655
+ .command("export")
656
+ .description("Export every collection in an app (name, owner, members, documents, group grants)")
657
+ .option("--app <app-id>", "App ID")
658
+ .option("--output <dir>", "Output directory", "./primitive-export")
659
+ .option("--json", "Output as JSON")
660
+ .addHelpText("after", `
661
+ Admin only: the export reads the app-wide collection listing.
662
+
663
+ It writes <output>/collections.json, beside the manifest.json and documents/
664
+ that 'documents export-all' writes, so one directory is one app's migration.
665
+ Import it with 'primitive collections import' AFTER 'documents import', which
666
+ preserves document ids.
667
+
668
+ Owner and member identities are recorded as emails wherever the server can
669
+ resolve one — users are app-scoped, so a source app's userId means nothing in
670
+ the target. A userId with no resolvable email is recorded as-is and will only
671
+ import back into the same app.
672
+
673
+ Not exported: collection ids (the server mints new ones), collection resource
674
+ metadata, the groups themselves, pending member invitations.
675
+ `)
676
+ .action(async (options) => {
677
+ const resolvedAppId = resolveAppId(undefined, options);
678
+ const client = new ApiClient();
679
+ try {
680
+ await exportCollections(client, resolvedAppId, options);
681
+ }
682
+ catch (err) {
683
+ error(err.message);
684
+ await flushOutput();
685
+ process.exit(1);
686
+ }
687
+ });
688
+ }
689
+ /** Follow every page of the admin collection listing. */
690
+ async function readAllCollections(client, appId) {
691
+ const rows = [];
692
+ let cursor;
693
+ do {
694
+ const page = await client.listAllCollections(appId, {
695
+ limit: 200,
696
+ ...(cursor ? { cursor } : {}),
697
+ });
698
+ rows.push(...(page.items ?? []));
699
+ cursor = page.nextCursor ?? undefined;
700
+ } while (cursor);
701
+ return rows;
702
+ }
703
+ /** Follow every page of one collection's document list. */
704
+ async function readAllCollectionDocuments(client, appId, collectionId) {
705
+ const rows = [];
706
+ let cursor;
707
+ do {
708
+ const page = await client.listCollectionDocuments(appId, collectionId, {
709
+ limit: 200,
710
+ ...(cursor ? { cursor } : {}),
711
+ });
712
+ for (const item of page.items ?? []) {
713
+ rows.push({
714
+ documentId: item.documentId,
715
+ addedAt: item.addedAt ?? null,
716
+ });
717
+ }
718
+ cursor = page.nextCursor ?? undefined;
719
+ } while (cursor);
720
+ return rows;
721
+ }
722
+ async function exportCollections(client, appId, options) {
723
+ const rows = await readAllCollections(client, appId);
724
+ // One lookup per DISTINCT userId across the whole run, cached: a busy app's
725
+ // owner appears on every collection it owns and again in each one's members.
726
+ const emailCache = new Map();
727
+ const emailFor = async (userId) => {
728
+ if (emailCache.has(userId))
729
+ return emailCache.get(userId);
730
+ let email;
731
+ try {
732
+ const found = await client.listUsers(appId, { userId, limit: 1 });
733
+ email = (found.items ?? [])[0]?.email || undefined;
734
+ }
735
+ catch {
736
+ // An unresolvable email is not a reason to lose the collection: the
737
+ // userId is still recorded, and a same-app import can still use it.
738
+ email = undefined;
739
+ }
740
+ emailCache.set(userId, email);
741
+ return email;
742
+ };
743
+ const identity = async (userId) => {
744
+ const email = await emailFor(userId);
745
+ return email ? { userId, email } : { userId };
746
+ };
747
+ const exported = [];
748
+ let skipped = 0;
749
+ for (const row of rows) {
750
+ const collectionId = row.collectionId;
751
+ try {
752
+ const documents = await readAllCollectionDocuments(client, appId, collectionId);
753
+ const access = await client.getCollectionAccess(appId, collectionId);
754
+ const members = [];
755
+ for (const m of access?.members ?? []) {
756
+ members.push({
757
+ ...(await identity(m.userId)),
758
+ permission: m.permission,
759
+ });
760
+ }
761
+ const groups = (access?.groups ?? [])
762
+ // The server already filters system groups out of this response; a
763
+ // `_`-prefixed type could never be re-granted anyway (it is reserved).
764
+ .filter((g) => !String(g.groupType).startsWith("_"))
765
+ .map((g) => ({
766
+ groupType: g.groupType,
767
+ groupId: g.groupId,
768
+ permission: g.permission,
769
+ }));
770
+ exported.push({
771
+ collectionId,
772
+ name: row.name,
773
+ description: row.description ?? null,
774
+ collectionType: row.collectionType ?? null,
775
+ contextId: row.contextId ?? null,
776
+ createdAt: row.createdAt ?? null,
777
+ owner: row.createdBy ? await identity(row.createdBy) : null,
778
+ documents,
779
+ members,
780
+ groups,
781
+ });
782
+ }
783
+ catch (err) {
784
+ skipped += 1;
785
+ warn(`Skipping collection "${row.name}" (${collectionId}): ${err.message}`);
786
+ }
787
+ }
788
+ const filePath = path.join(options.output, COLLECTION_EXPORT_FILENAME);
789
+ // Each run REPLACES the file. A collection the previous one listed and this
790
+ // one does not becomes unreachable through the file an import reads, so say
791
+ // so — the same warning `documents export-all` gives for its manifest.
792
+ if (fs.existsSync(filePath)) {
793
+ try {
794
+ const previous = JSON.parse(fs.readFileSync(filePath, "utf-8"));
795
+ const kept = new Set(exported.map((c) => c.collectionId));
796
+ const dropped = (Array.isArray(previous?.collections) ? previous.collections : []).filter((c) => !kept.has(c?.collectionId));
797
+ if (dropped.length > 0) {
798
+ warn(`The ${COLLECTION_EXPORT_FILENAME} already in ${options.output} lists ` +
799
+ `${dropped.length} collection(s) this export does not: ` +
800
+ `${dropped.map((c) => c?.name ?? c?.collectionId).join(", ")}. ` +
801
+ `Replacing it leaves them unreachable through the file an import reads.`);
802
+ }
803
+ }
804
+ catch {
805
+ // An unreadable previous file is not a reason to refuse the export.
806
+ }
807
+ }
808
+ const file = {
809
+ version: COLLECTION_EXPORT_VERSION,
810
+ exportedAt: new Date().toISOString(),
811
+ sourceAppId: appId,
812
+ collectionCount: exported.length,
813
+ collections: exported,
814
+ };
815
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
816
+ fs.writeFileSync(filePath, JSON.stringify(file, null, 2));
817
+ if (options.json) {
818
+ // Counts and the path, not the bodies: the file is where those live.
819
+ json({
820
+ version: file.version,
821
+ exportedAt: file.exportedAt,
822
+ sourceAppId: file.sourceAppId,
823
+ collectionCount: file.collectionCount,
824
+ skipped,
825
+ path: filePath,
826
+ });
827
+ return;
828
+ }
829
+ success(`Exported ${exported.length} collection(s) to ${filePath}`);
830
+ }
831
+ // ---------------------------------------------------------------------------
832
+ // Import (#3643)
833
+ // ---------------------------------------------------------------------------
834
+ /**
835
+ * `primitive collections import` — the other half of the migration.
836
+ *
837
+ * Two phases, in this order and never interleaved:
838
+ *
839
+ * 1. RESOLVE, reads only. Every owner, member, document and group the file
840
+ * names is looked up in the target app and the plan is decided from the
841
+ * answers, before anything at all is written. Only a DEFINITIVE absence
842
+ * (a 404, or `exists: false` from the email lookup) reads as "not here";
843
+ * any other failure fails the run with the collection and the item named,
844
+ * exactly as `documents import` decides its root-document plans (#3096).
845
+ * 2. APPLY, in the order create → group grants → members → documents. Grants
846
+ * go first ON PURPOSE: `addDocument` materializes every existing grant
847
+ * onto a newly added document, so a fresh collection's access never
848
+ * depends on the grant's own fan-out over the document list (F4).
849
+ */
850
+ function registerCollectionsImport(collections) {
851
+ collections
852
+ .command("import")
853
+ .description("Recreate collections from a collections.json export (owner, members, documents, group grants)")
854
+ .argument("<path>", "Export directory holding collections.json, or the file itself")
855
+ .option("--app <app-id>", "App ID")
856
+ .option("--owner <userId-or-email>", "Own every imported collection with this user, instead of the owner each one records")
857
+ .option("--overwrite", "Merge into a collection that already carries the same name")
858
+ .option("--dry-run", "Show the plan and the problems without writing anything")
859
+ .option("--json", "Output as JSON")
860
+ .addHelpText("after", `
861
+ For admin tokens. Assigning an owner is admin-only, so under an app-user token
862
+ the server creates every collection owned by the caller and each one whose
863
+ recorded owner is somebody else is reported as an 'owner' problem.
864
+
865
+ Run 'primitive documents import' FIRST: it preserves document ids, which is
866
+ what the collections file refers to.
867
+
868
+ Owners and members are resolved by EMAIL — users are app-scoped, so a source
869
+ app's userId means nothing here. A recorded userId is used only when the file
870
+ came from this same app.
871
+
872
+ Matching is by name. Without --overwrite a name already in the target is
873
+ skipped; with it, the collection is merged — but only when its owner, type and
874
+ context id match, since none of those can be changed after creation.
875
+
876
+ A group grant the target already holds at another level is UPDATED to the level
877
+ the file records, and what the group can reach follows the change. On a
878
+ collection holding more than 200 documents the server refuses a collection-wide
879
+ group change whole (COLLECTION_FANOUT_LIMIT): that is reported as a 'group'
880
+ problem, and the rest of the collection is still merged.
881
+
882
+ Exit code 1 when any per-item problem was reported. A skip is not a problem.
883
+ `)
884
+ .action(async (importPath, options) => {
885
+ const resolvedAppId = resolveAppId(undefined, options);
886
+ const client = new ApiClient();
887
+ let code = 0;
888
+ try {
889
+ code = await importCollections(client, resolvedAppId, importPath, options);
890
+ }
891
+ catch (err) {
892
+ error(err.message);
893
+ code = 1;
894
+ }
895
+ await flushOutput();
896
+ if (code !== 0)
897
+ process.exit(code);
898
+ });
899
+ }
900
+ /** A definitive "not here", as opposed to a read that simply failed. */
901
+ function isNotFound(err) {
902
+ return err?.statusCode === 404;
903
+ }
904
+ async function importCollections(client, appId, importPath, options) {
905
+ const file = readCollectionExportFile(importPath);
906
+ // A source userId is only meaningful when the file came from THIS app —
907
+ // users are tenant-scoped, so the same person has a different userId in
908
+ // every app (`models.yaml`).
909
+ const sameApp = file.sourceAppId === appId;
910
+ /** userId ← recorded userId: the admin read, cached, one per distinct id. */
911
+ const byUserId = new Map();
912
+ const resolveRecordedUserId = async (userId) => {
913
+ if (byUserId.has(userId))
914
+ return byUserId.get(userId);
915
+ const found = await client.listUsers(appId, { userId, limit: 1 });
916
+ const hit = (found.items ?? [])[0];
917
+ // A row with no role is a user who is no longer in this app.
918
+ const resolved = hit && hit.role ? hit.userId : null;
919
+ byUserId.set(userId, resolved);
920
+ return resolved;
921
+ };
922
+ // `--owner` is resolved ONCE, before a single collection of the file is
923
+ // looked at: a name nobody answers to must fail the run with nothing written.
924
+ let explicitOwnerUserId;
925
+ if (options.owner) {
926
+ explicitOwnerUserId = await resolveOwnerUserId(client, appId, options.owner);
927
+ if (!options.owner.includes("@")) {
928
+ // `resolveOwnerUserId` hands a value with no "@" straight back — it is a
929
+ // userId as far as the flag is concerned, but nothing has yet checked
930
+ // that this app has such a member. Left unchecked, an id nobody answers
931
+ // to planned a create for every collection, `--dry-run` reported that
932
+ // plan as fine, and only the real run found out, one failed create at a
933
+ // time. The flag fails the run here instead, the way an unknown email
934
+ // already does.
935
+ let found;
936
+ try {
937
+ found = await resolveRecordedUserId(explicitOwnerUserId);
938
+ }
939
+ catch (err) {
940
+ throw new Error(`Checking --owner ${explicitOwnerUserId} against app ${appId} ` +
941
+ `failed: ${err?.message ?? err}. Nothing has been written.`);
942
+ }
943
+ if (!found) {
944
+ throw new Error(`No app user found with id "${explicitOwnerUserId}" in app ${appId}.`);
945
+ }
946
+ }
947
+ }
948
+ const problems = [];
949
+ const summary = emptyImportSummary();
950
+ // ── Resolve phase ──────────────────────────────────────────────────────
951
+ const targetByName = new Map();
952
+ for (const row of await readAllCollections(client, appId)) {
953
+ if (!targetByName.has(row.name))
954
+ targetByName.set(row.name, row);
955
+ }
956
+ /** userId ← email, cached: one lookup per distinct email per run. */
957
+ const byEmail = new Map();
958
+ const resolveEmail = async (email) => {
959
+ if (byEmail.has(email))
960
+ return byEmail.get(email);
961
+ const lookup = await client.lookupUserByEmail(appId, email);
962
+ const userId = lookup?.exists && lookup.user ? lookup.user.userId : null;
963
+ byEmail.set(email, userId);
964
+ return userId;
965
+ };
966
+ /**
967
+ * One identity from the file to a userId in the target app.
968
+ *
969
+ * `{ userId: null, reason }` is a definitive absence to report; anything the
970
+ * reads could not settle has already thrown by the time this returns.
971
+ */
972
+ const resolveIdentity = async (identity) => {
973
+ if (!identity) {
974
+ return { userId: null, reason: "the export records no identity for it", id: "(none)" };
975
+ }
976
+ const id = identity.email || identity.userId || "(none)";
977
+ if (identity.email) {
978
+ const userId = await resolveEmail(identity.email);
979
+ return userId
980
+ ? { userId, id }
981
+ : {
982
+ userId: null,
983
+ id,
984
+ reason: `no user with email "${identity.email}" is a member of ${appId}`,
985
+ };
986
+ }
987
+ if (identity.userId && sameApp) {
988
+ const userId = await resolveRecordedUserId(identity.userId);
989
+ return userId
990
+ ? { userId, id }
991
+ : {
992
+ userId: null,
993
+ id,
994
+ reason: `no user with id ${identity.userId} is a member of ${appId}`,
995
+ };
996
+ }
997
+ return {
998
+ userId: null,
999
+ id,
1000
+ reason: `it records only a userId from app ${file.sourceAppId || "(unknown)"}, ` +
1001
+ `and users are scoped to their app — the same person has a different ` +
1002
+ `userId here, so only an email can identify them across apps`,
1003
+ };
1004
+ };
1005
+ const duplicates = duplicateNameSources(file.collections);
1006
+ const planned = [];
1007
+ for (const source of file.collections) {
1008
+ const note = (kind, id, reason) => {
1009
+ problems.push({
1010
+ sourceCollectionId: source.collectionId,
1011
+ name: source.name,
1012
+ kind,
1013
+ id,
1014
+ reason,
1015
+ });
1016
+ warn(`${source.name}: ${kind} ${id} — ${reason}`);
1017
+ };
1018
+ /** Any read that is not a definitive absence stops the whole run. */
1019
+ const fatal = (item, err) => {
1020
+ throw new Error(`Reading ${item} for collection "${source.name}" ` +
1021
+ `(${source.collectionId}) failed: ${err?.message ?? err}. ` +
1022
+ `Nothing has been written.`);
1023
+ };
1024
+ let ownerUserId = explicitOwnerUserId ?? null;
1025
+ let ownerReason;
1026
+ if (!ownerUserId) {
1027
+ try {
1028
+ const resolved = await resolveIdentity(source.owner);
1029
+ ownerUserId = resolved.userId;
1030
+ ownerReason = resolved.reason;
1031
+ }
1032
+ catch (err) {
1033
+ fatal(`owner ${source.owner?.email ?? source.owner?.userId ?? ""}`, err);
1034
+ }
1035
+ }
1036
+ const existingRow = targetByName.get(source.name);
1037
+ const row = planCollection(source, {
1038
+ ownerUserId,
1039
+ ownerReason,
1040
+ existing: existingRow
1041
+ ? {
1042
+ collectionId: existingRow.collectionId,
1043
+ createdBy: existingRow.createdBy,
1044
+ collectionType: existingRow.collectionType ?? null,
1045
+ contextId: existingRow.contextId ?? null,
1046
+ }
1047
+ : null,
1048
+ duplicateOfSourceId: duplicates.get(source.collectionId) ?? null,
1049
+ }, { overwrite: Boolean(options.overwrite) });
1050
+ if (row.problem) {
1051
+ note(row.problem.kind, row.problem.id, row.problem.reason);
1052
+ }
1053
+ if (row.action === "refused" || row.action === "skip") {
1054
+ planned.push({ row, source, grants: [], members: [], documents: [] });
1055
+ continue;
1056
+ }
1057
+ // What the target already grants, so a merge can compare levels.
1058
+ let targetGrants = [];
1059
+ if (row.action === "merge" && row.targetCollectionId) {
1060
+ try {
1061
+ const access = await client.getCollectionAccess(appId, row.targetCollectionId);
1062
+ targetGrants = (access?.groups ?? []).map((g) => ({
1063
+ groupType: g.groupType,
1064
+ groupId: g.groupId,
1065
+ permission: g.permission,
1066
+ }));
1067
+ }
1068
+ catch (err) {
1069
+ fatal(`the access of ${row.targetCollectionId}`, err);
1070
+ }
1071
+ }
1072
+ const grants = [];
1073
+ for (const grant of source.groups ?? []) {
1074
+ const label = `${grant.groupType}/${grant.groupId}`;
1075
+ // The server reserves the `_` prefix for its own system groups; an
1076
+ // export never writes one, and re-granting one is a 400.
1077
+ if (String(grant.groupType).startsWith("_")) {
1078
+ warn(`${source.name}: skipping the reserved group type ${grant.groupType} ` +
1079
+ `(${label}); the server manages those itself.`);
1080
+ continue;
1081
+ }
1082
+ try {
1083
+ await client.getGroup(appId, grant.groupType, grant.groupId);
1084
+ }
1085
+ catch (err) {
1086
+ if (isNotFound(err)) {
1087
+ note("group", label, `no group ${label} exists in ${appId}`);
1088
+ continue;
1089
+ }
1090
+ fatal(`group ${label}`, err);
1091
+ }
1092
+ const disposition = grantDisposition(grant, targetGrants);
1093
+ const existing = targetGrants.find((g) => g.groupType === grant.groupType && g.groupId === grant.groupId);
1094
+ grants.push({
1095
+ grant,
1096
+ disposition,
1097
+ ...(existing ? { existingPermission: existing.permission } : {}),
1098
+ });
1099
+ }
1100
+ const members = [];
1101
+ for (const member of source.members ?? []) {
1102
+ let resolved;
1103
+ try {
1104
+ resolved = await resolveIdentity(member);
1105
+ }
1106
+ catch (err) {
1107
+ fatal(`member ${member.email ?? member.userId}`, err);
1108
+ continue;
1109
+ }
1110
+ if (!resolved.userId) {
1111
+ note("member", resolved.id, resolved.reason);
1112
+ continue;
1113
+ }
1114
+ members.push({ userId: resolved.userId, permission: member.permission });
1115
+ }
1116
+ const documents = [];
1117
+ for (const document of source.documents ?? []) {
1118
+ try {
1119
+ await client.getDocument(appId, document.documentId);
1120
+ }
1121
+ catch (err) {
1122
+ if (isNotFound(err)) {
1123
+ note("document", document.documentId, `no document ${document.documentId} exists in ${appId}; ` +
1124
+ `run 'primitive documents import' first — it preserves ids`);
1125
+ continue;
1126
+ }
1127
+ fatal(`document ${document.documentId}`, err);
1128
+ }
1129
+ documents.push(document.documentId);
1130
+ }
1131
+ planned.push({ row, source, grants, members, documents });
1132
+ }
1133
+ // ── Apply phase ────────────────────────────────────────────────────────
1134
+ for (const entry of planned) {
1135
+ const { row, source } = entry;
1136
+ const note = (kind, id, reason) => {
1137
+ problems.push({
1138
+ sourceCollectionId: source.collectionId,
1139
+ name: source.name,
1140
+ kind,
1141
+ id,
1142
+ reason,
1143
+ });
1144
+ warn(`${source.name}: ${kind} ${id} — ${reason}`);
1145
+ };
1146
+ if (row.action === "refused") {
1147
+ summary.refused += 1;
1148
+ info(`Refusing ${source.name}: ${row.reason}`);
1149
+ continue;
1150
+ }
1151
+ if (row.action === "skip") {
1152
+ summary.skipped += 1;
1153
+ info(`Skipping ${source.name}: ${row.reason}`);
1154
+ continue;
1155
+ }
1156
+ if (options.dryRun) {
1157
+ // The plan, and nothing else: not one write is issued.
1158
+ if (row.action === "create") {
1159
+ summary.created += 1;
1160
+ info(`[dry-run] Would create ${source.name}`);
1161
+ }
1162
+ else {
1163
+ summary.merged += 1;
1164
+ info(`[dry-run] Would merge ${source.name} into ${row.targetCollectionId}`);
1165
+ }
1166
+ for (const { grant, disposition, existingPermission } of entry.grants) {
1167
+ if (disposition === "already-applied")
1168
+ continue;
1169
+ const label = `${grant.groupType}/${grant.groupId}`;
1170
+ info(disposition === "update"
1171
+ ? `[dry-run] Would update ${grantUpdateLine(label, existingPermission, grant.permission)}`
1172
+ : `[dry-run] Would grant ${label} ${grant.permission}`);
1173
+ summary.groupsGranted += 1;
1174
+ }
1175
+ summary.membersAdded += entry.members.length;
1176
+ summary.documentsAdded += entry.documents.length;
1177
+ continue;
1178
+ }
1179
+ let targetCollectionId = row.targetCollectionId;
1180
+ if (row.action === "create") {
1181
+ try {
1182
+ const created = await client.createCollection(appId, {
1183
+ name: source.name,
1184
+ ...(source.description ? { description: source.description } : {}),
1185
+ ...(source.collectionType ? { collectionType: source.collectionType } : {}),
1186
+ ...(source.contextId ? { contextId: source.contextId } : {}),
1187
+ createdBy: row.ownerUserId,
1188
+ });
1189
+ targetCollectionId = created.collectionId;
1190
+ row.targetCollectionId = targetCollectionId;
1191
+ summary.created += 1;
1192
+ info(`Created ${source.name} as ${targetCollectionId}`);
1193
+ // Whom the server CREDITED is the answer. A server predating #3641
1194
+ // ignores the field, and so does any server for a non-admin token.
1195
+ if (created.createdBy && created.createdBy !== row.ownerUserId) {
1196
+ note("owner", row.ownerUserId, `the server did not assign the owner (it credited ` +
1197
+ `${created.createdBy}); the token is not an admin one, or the ` +
1198
+ `server predates #3641`);
1199
+ }
1200
+ }
1201
+ catch (err) {
1202
+ note("collection", source.collectionId, serverReason(err));
1203
+ continue;
1204
+ }
1205
+ }
1206
+ else {
1207
+ summary.merged += 1;
1208
+ info(`Merged ${source.name} into ${targetCollectionId}`);
1209
+ }
1210
+ for (const { grant, disposition, existingPermission } of entry.grants) {
1211
+ if (disposition === "already-applied")
1212
+ continue;
1213
+ const label = `${grant.groupType}/${grant.groupId}`;
1214
+ try {
1215
+ await client.grantCollectionGroupPermission(appId, targetCollectionId, {
1216
+ groupType: grant.groupType,
1217
+ groupId: grant.groupId,
1218
+ permission: grant.permission,
1219
+ });
1220
+ summary.groupsGranted += 1;
1221
+ // A level change is worth saying out loud: it is the one grant write
1222
+ // that can take access away from people who already had it.
1223
+ if (disposition === "update") {
1224
+ info(`Updated ${grantUpdateLine(label, existingPermission, grant.permission)}`);
1225
+ }
1226
+ }
1227
+ catch (err) {
1228
+ note("group", label, serverReason(err));
1229
+ }
1230
+ }
1231
+ for (const member of entry.members) {
1232
+ try {
1233
+ const result = await client.addCollectionMember(appId, targetCollectionId, {
1234
+ userId: member.userId,
1235
+ permission: member.permission,
1236
+ });
1237
+ // `already_member` is the server saying it was already so — including
1238
+ // the owner's own `_col-writer` enrolment, which every export records.
1239
+ if (result?.status !== "already_member")
1240
+ summary.membersAdded += 1;
1241
+ }
1242
+ catch (err) {
1243
+ note("member", member.userId, serverReason(err));
1244
+ }
1245
+ }
1246
+ for (const documentId of entry.documents) {
1247
+ try {
1248
+ await client.addCollectionDocument(appId, targetCollectionId, { documentId });
1249
+ summary.documentsAdded += 1;
1250
+ }
1251
+ catch (err) {
1252
+ // 409 is "already in this collection": applied, not a problem.
1253
+ if (err?.statusCode === 409)
1254
+ continue;
1255
+ note("document", documentId, serverReason(err));
1256
+ }
1257
+ }
1258
+ }
1259
+ if (options.json) {
1260
+ json({
1261
+ ...summary,
1262
+ collections: planned.map(({ row }) => ({
1263
+ sourceCollectionId: row.sourceCollectionId,
1264
+ name: row.name,
1265
+ action: row.action,
1266
+ ...(row.targetCollectionId
1267
+ ? { targetCollectionId: row.targetCollectionId }
1268
+ : {}),
1269
+ ...(row.reason ? { reason: row.reason } : {}),
1270
+ })),
1271
+ problems,
1272
+ });
1273
+ }
1274
+ else {
1275
+ success(`${summary.created} created, ${summary.merged} merged, ` +
1276
+ `${summary.skipped} skipped, ${summary.refused} refused; ` +
1277
+ `${summary.groupsGranted} group grant(s), ${summary.membersAdded} member(s), ` +
1278
+ `${summary.documentsAdded} document(s) added; ${problems.length} problem(s).`);
1279
+ }
1280
+ return importExitCode(problems);
1281
+ }
1282
+ //# sourceMappingURL=collections.js.map