@columnus/shared 0.1.47

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 (206) hide show
  1. package/LICENSE +21 -0
  2. package/dist/apiProfile.d.ts +1072 -0
  3. package/dist/apiProfile.js +4210 -0
  4. package/dist/apiProfile.js.map +1 -0
  5. package/dist/apiProfileSlug.d.ts +11 -0
  6. package/dist/apiProfileSlug.js +39 -0
  7. package/dist/apiProfileSlug.js.map +1 -0
  8. package/dist/apiToken.d.ts +6 -0
  9. package/dist/apiToken.js +35 -0
  10. package/dist/apiToken.js.map +1 -0
  11. package/dist/betterAuthSession.d.ts +8 -0
  12. package/dist/betterAuthSession.js +57 -0
  13. package/dist/betterAuthSession.js.map +1 -0
  14. package/dist/bffHost.d.ts +36 -0
  15. package/dist/bffHost.js +74 -0
  16. package/dist/bffHost.js.map +1 -0
  17. package/dist/bffProtocol.d.ts +1357 -0
  18. package/dist/bffProtocol.js +4362 -0
  19. package/dist/bffProtocol.js.map +1 -0
  20. package/dist/bffRedact.d.ts +30 -0
  21. package/dist/bffRedact.js +55 -0
  22. package/dist/bffRedact.js.map +1 -0
  23. package/dist/ciPreviewEnv.d.ts +49 -0
  24. package/dist/ciPreviewEnv.js +143 -0
  25. package/dist/ciPreviewEnv.js.map +1 -0
  26. package/dist/clipboardTransfer.d.ts +25 -0
  27. package/dist/clipboardTransfer.js +4862 -0
  28. package/dist/clipboardTransfer.js.map +1 -0
  29. package/dist/convexSiteUrl.d.ts +73 -0
  30. package/dist/convexSiteUrl.js +137 -0
  31. package/dist/convexSiteUrl.js.map +1 -0
  32. package/dist/csv.d.ts +54 -0
  33. package/dist/csv.js +5499 -0
  34. package/dist/csv.js.map +1 -0
  35. package/dist/dataTransfer.d.ts +21 -0
  36. package/dist/dataTransfer.js +74 -0
  37. package/dist/dataTransfer.js.map +1 -0
  38. package/dist/dateValues.d.ts +18 -0
  39. package/dist/dateValues.js +73 -0
  40. package/dist/dateValues.js.map +1 -0
  41. package/dist/deployOutput.d.ts +3 -0
  42. package/dist/deployOutput.js +23 -0
  43. package/dist/deployOutput.js.map +1 -0
  44. package/dist/displayLabel.d.ts +13 -0
  45. package/dist/displayLabel.js +4342 -0
  46. package/dist/displayLabel.js.map +1 -0
  47. package/dist/displayTemplateGenerator.d.ts +11 -0
  48. package/dist/displayTemplateGenerator.js +98 -0
  49. package/dist/displayTemplateGenerator.js.map +1 -0
  50. package/dist/displayTypes.d.ts +10 -0
  51. package/dist/displayTypes.js +3 -0
  52. package/dist/displayTypes.js.map +1 -0
  53. package/dist/dotenv.d.ts +8 -0
  54. package/dist/dotenv.js +61 -0
  55. package/dist/dotenv.js.map +1 -0
  56. package/dist/erdLayout.d.ts +27 -0
  57. package/dist/erdLayout.js +78 -0
  58. package/dist/erdLayout.js.map +1 -0
  59. package/dist/fieldTypeFormat.d.ts +5 -0
  60. package/dist/fieldTypeFormat.js +63 -0
  61. package/dist/fieldTypeFormat.js.map +1 -0
  62. package/dist/fieldTypeMeta.d.ts +50 -0
  63. package/dist/fieldTypeMeta.js +84 -0
  64. package/dist/fieldTypeMeta.js.map +1 -0
  65. package/dist/fieldValueEqual.d.ts +4 -0
  66. package/dist/fieldValueEqual.js +58 -0
  67. package/dist/fieldValueEqual.js.map +1 -0
  68. package/dist/importMatch.d.ts +80 -0
  69. package/dist/importMatch.js +5643 -0
  70. package/dist/importMatch.js.map +1 -0
  71. package/dist/index.d.ts +58 -0
  72. package/dist/index.js +10647 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/inferIdentifier.d.ts +18 -0
  75. package/dist/inferIdentifier.js +49 -0
  76. package/dist/inferIdentifier.js.map +1 -0
  77. package/dist/keyboard.d.ts +16 -0
  78. package/dist/keyboard.js +71 -0
  79. package/dist/keyboard.js.map +1 -0
  80. package/dist/manifestDeploy.d.ts +27 -0
  81. package/dist/manifestDeploy.js +16 -0
  82. package/dist/manifestDeploy.js.map +1 -0
  83. package/dist/manifestHash.d.ts +11 -0
  84. package/dist/manifestHash.js +35 -0
  85. package/dist/manifestHash.js.map +1 -0
  86. package/dist/previewDeployName.d.ts +26 -0
  87. package/dist/previewDeployName.js +77 -0
  88. package/dist/previewDeployName.js.map +1 -0
  89. package/dist/proposePolicy.d.ts +17 -0
  90. package/dist/proposePolicy.js +33 -0
  91. package/dist/proposePolicy.js.map +1 -0
  92. package/dist/queryBetween.d.ts +20 -0
  93. package/dist/queryBetween.js +28 -0
  94. package/dist/queryBetween.js.map +1 -0
  95. package/dist/queryExecution.d.ts +5 -0
  96. package/dist/queryExecution.js +58 -0
  97. package/dist/queryExecution.js.map +1 -0
  98. package/dist/queryFieldOps.d.ts +66 -0
  99. package/dist/queryFieldOps.js +138 -0
  100. package/dist/queryFieldOps.js.map +1 -0
  101. package/dist/queryMerge.d.ts +29 -0
  102. package/dist/queryMerge.js +4277 -0
  103. package/dist/queryMerge.js.map +1 -0
  104. package/dist/queryPlan.d.ts +97 -0
  105. package/dist/queryPlan.js +4564 -0
  106. package/dist/queryPlan.js.map +1 -0
  107. package/dist/queryStudio.d.ts +25 -0
  108. package/dist/queryStudio.js +4658 -0
  109. package/dist/queryStudio.js.map +1 -0
  110. package/dist/queryTypes.d.ts +402 -0
  111. package/dist/queryTypes.js +4239 -0
  112. package/dist/queryTypes.js.map +1 -0
  113. package/dist/queryValidators.d.ts +99 -0
  114. package/dist/queryValidators.js +76 -0
  115. package/dist/queryValidators.js.map +1 -0
  116. package/dist/readApiQuery.d.ts +10 -0
  117. package/dist/readApiQuery.js +24 -0
  118. package/dist/readApiQuery.js.map +1 -0
  119. package/dist/recordTableColumns.d.ts +21 -0
  120. package/dist/recordTableColumns.js +4469 -0
  121. package/dist/recordTableColumns.js.map +1 -0
  122. package/dist/refDisplayRequests.d.ts +17 -0
  123. package/dist/refDisplayRequests.js +4877 -0
  124. package/dist/refDisplayRequests.js.map +1 -0
  125. package/dist/refGraph.d.ts +23 -0
  126. package/dist/refGraph.js +102 -0
  127. package/dist/refGraph.js.map +1 -0
  128. package/dist/refsRecompute.d.ts +8 -0
  129. package/dist/refsRecompute.js +12 -0
  130. package/dist/refsRecompute.js.map +1 -0
  131. package/dist/richDoc.d.ts +67 -0
  132. package/dist/richDoc.js +4408 -0
  133. package/dist/richDoc.js.map +1 -0
  134. package/dist/schemaGenerator.d.ts +31 -0
  135. package/dist/schemaGenerator.js +198 -0
  136. package/dist/schemaGenerator.js.map +1 -0
  137. package/dist/schemaShrink.d.ts +74 -0
  138. package/dist/schemaShrink.js +132 -0
  139. package/dist/schemaShrink.js.map +1 -0
  140. package/dist/schemaShrinkValidators.d.ts +72 -0
  141. package/dist/schemaShrinkValidators.js +31 -0
  142. package/dist/schemaShrinkValidators.js.map +1 -0
  143. package/dist/schemaTypes.d.ts +454 -0
  144. package/dist/schemaTypes.js +4557 -0
  145. package/dist/schemaTypes.js.map +1 -0
  146. package/dist/share.d.ts +8 -0
  147. package/dist/share.js +4755 -0
  148. package/dist/share.js.map +1 -0
  149. package/dist/shareApiBridge.d.ts +18 -0
  150. package/dist/shareApiBridge.js +4283 -0
  151. package/dist/shareApiBridge.js.map +1 -0
  152. package/dist/shareGuestPolicy.d.ts +14 -0
  153. package/dist/shareGuestPolicy.js +90 -0
  154. package/dist/shareGuestPolicy.js.map +1 -0
  155. package/dist/shareProposal.d.ts +113 -0
  156. package/dist/shareProposal.js +4810 -0
  157. package/dist/shareProposal.js.map +1 -0
  158. package/dist/shareProposalDiff.d.ts +13 -0
  159. package/dist/shareProposalDiff.js +91 -0
  160. package/dist/shareProposalDiff.js.map +1 -0
  161. package/dist/shareScopes.d.ts +14 -0
  162. package/dist/shareScopes.js +223 -0
  163. package/dist/shareScopes.js.map +1 -0
  164. package/dist/shareTokenCrypto.d.ts +9 -0
  165. package/dist/shareTokenCrypto.js +87 -0
  166. package/dist/shareTokenCrypto.js.map +1 -0
  167. package/dist/shareTokens.d.ts +6 -0
  168. package/dist/shareTokens.js +26 -0
  169. package/dist/shareTokens.js.map +1 -0
  170. package/dist/shareTypes.d.ts +107 -0
  171. package/dist/shareTypes.js +3 -0
  172. package/dist/shareTypes.js.map +1 -0
  173. package/dist/shareValidators.d.ts +168 -0
  174. package/dist/shareValidators.js +124 -0
  175. package/dist/shareValidators.js.map +1 -0
  176. package/dist/shareWizard.d.ts +14 -0
  177. package/dist/shareWizard.js +200 -0
  178. package/dist/shareWizard.js.map +1 -0
  179. package/dist/spaceBundle.d.ts +67 -0
  180. package/dist/spaceBundle.js +4664 -0
  181. package/dist/spaceBundle.js.map +1 -0
  182. package/dist/storageFieldValues.d.ts +10 -0
  183. package/dist/storageFieldValues.js +4421 -0
  184. package/dist/storageFieldValues.js.map +1 -0
  185. package/dist/studioBoot.d.ts +49 -0
  186. package/dist/studioBoot.js +167 -0
  187. package/dist/studioBoot.js.map +1 -0
  188. package/dist/syncClampMembership.d.ts +22 -0
  189. package/dist/syncClampMembership.js +74 -0
  190. package/dist/syncClampMembership.js.map +1 -0
  191. package/dist/tenantHosts.d.ts +30 -0
  192. package/dist/tenantHosts.js +60 -0
  193. package/dist/tenantHosts.js.map +1 -0
  194. package/dist/tenantSecretsCrypto.d.ts +31 -0
  195. package/dist/tenantSecretsCrypto.js +145 -0
  196. package/dist/tenantSecretsCrypto.js.map +1 -0
  197. package/dist/useDebouncedCommit.d.ts +11 -0
  198. package/dist/useDebouncedCommit.js +34 -0
  199. package/dist/useDebouncedCommit.js.map +1 -0
  200. package/dist/useDebouncedValue.d.ts +1 -0
  201. package/dist/useDebouncedValue.js +15 -0
  202. package/dist/useDebouncedValue.js.map +1 -0
  203. package/dist/useModifierHeld.d.ts +2 -0
  204. package/dist/useModifierHeld.js +28 -0
  205. package/dist/useModifierHeld.js.map +1 -0
  206. package/package.json +36 -0
@@ -0,0 +1,14 @@
1
+ import type { GuestCapabilities, GuestCapabilityInput, GuestLinkCapabilities, GuestScopeCapabilities, ShareScope } from "./shareTypes.ts";
2
+ export declare function resolveGuestLinkCapabilities(input: GuestCapabilityInput): GuestLinkCapabilities;
3
+ export declare function resolveGuestScopeCapabilities(input: GuestCapabilityInput & {
4
+ scope: ShareScope;
5
+ }): GuestScopeCapabilities;
6
+ export declare function resolveGuestCapabilities(input: GuestCapabilityInput & {
7
+ scope?: ShareScope;
8
+ }): GuestCapabilities;
9
+ export declare function guestVisibleScopes(input: GuestCapabilityInput & {
10
+ scopes: ShareScope[];
11
+ }): ShareScope[];
12
+ export declare function guestSearchableScopes(input: GuestCapabilityInput & {
13
+ scopes: ShareScope[];
14
+ }): ShareScope[];
@@ -0,0 +1,90 @@
1
+ // packages/@columnus/shared/src/shareScopes.ts
2
+ function isReferenceOnlyScope(scope) {
3
+ return scope.primary === false;
4
+ }
5
+ function isFormSubmissionScope(scope) {
6
+ return scope.formAccess?.submit === true;
7
+ }
8
+ function canSearchFormReferences(scope) {
9
+ return scope.formAccess?.searchReferences === true;
10
+ }
11
+ function canCreateFormReferences(scope) {
12
+ return scope?.formAccess?.createReferences === true;
13
+ }
14
+ function canProposeOperation(scope, operation) {
15
+ if (scope?.permission !== "propose" || !scope.ops[operation]) return false;
16
+ return operation !== "insert" || scope.access !== "referenced" || Boolean(scope.allowCreate);
17
+ }
18
+ function canProposeInsert(scope) {
19
+ return canProposeOperation(scope, "insert");
20
+ }
21
+
22
+ // packages/@columnus/shared/src/shareGuestPolicy.ts
23
+ function resolveGuestLinkCapabilities(input) {
24
+ const { type } = input.link;
25
+ const recipientPropose = type === "propose" && input.invite.kind === "recipient";
26
+ return {
27
+ formMode: type === "form",
28
+ canBrowseRows: type === "view" || recipientPropose,
29
+ canListProposals: recipientPropose,
30
+ canUpsertPatches: type === "propose",
31
+ canToggleViewMode: type !== "form"
32
+ };
33
+ }
34
+ function resolveGuestScopeCapabilities(input) {
35
+ const link = resolveGuestLinkCapabilities(input);
36
+ const { scope } = input;
37
+ const propose = {
38
+ canProposeInsert: canProposeInsert(scope),
39
+ canProposeUpdate: link.canUpsertPatches && canProposeOperation(scope, "update"),
40
+ canProposeDelete: link.canUpsertPatches && canProposeOperation(scope, "delete")
41
+ };
42
+ if (link.formMode) {
43
+ const canSearch = canSearchFormReferences(scope);
44
+ return {
45
+ visibleInNav: isFormSubmissionScope(scope),
46
+ canReadDisplay: canSearch,
47
+ canSearchReferences: canSearch,
48
+ canCreateReferences: canCreateFormReferences(scope),
49
+ ...propose
50
+ };
51
+ }
52
+ if (isReferenceOnlyScope(scope)) {
53
+ const canSearch = canSearchFormReferences(scope);
54
+ const canCreate = canCreateFormReferences(scope);
55
+ return {
56
+ visibleInNav: false,
57
+ canReadDisplay: canSearch || canCreate,
58
+ canSearchReferences: canSearch,
59
+ canCreateReferences: canCreate,
60
+ ...propose
61
+ };
62
+ }
63
+ return {
64
+ visibleInNav: true,
65
+ canReadDisplay: true,
66
+ canSearchReferences: true,
67
+ canCreateReferences: canProposeInsert(scope),
68
+ ...propose
69
+ };
70
+ }
71
+ function resolveGuestCapabilities(input) {
72
+ return {
73
+ link: resolveGuestLinkCapabilities(input),
74
+ scope: input.scope ? resolveGuestScopeCapabilities({ ...input, scope: input.scope }) : void 0
75
+ };
76
+ }
77
+ function guestVisibleScopes(input) {
78
+ return input.scopes.filter(
79
+ (scope) => resolveGuestScopeCapabilities({ ...input, scope }).visibleInNav
80
+ );
81
+ }
82
+ function guestSearchableScopes(input) {
83
+ return input.scopes.filter(
84
+ (scope) => resolveGuestScopeCapabilities({ ...input, scope }).canSearchReferences
85
+ );
86
+ }
87
+
88
+ export { guestSearchableScopes, guestVisibleScopes, resolveGuestCapabilities, resolveGuestLinkCapabilities, resolveGuestScopeCapabilities };
89
+ //# sourceMappingURL=shareGuestPolicy.js.map
90
+ //# sourceMappingURL=shareGuestPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shareScopes.ts","../src/shareGuestPolicy.ts"],"names":[],"mappings":";AA2DO,SAAS,qBAAqB,KAAA,EAA4B;AAC/D,EAAA,OAAO,MAAM,OAAA,KAAY,KAAA;AAC3B;AA4CO,SAAS,sBAAsB,KAAA,EAA4B;AAChE,EAAA,OAAO,KAAA,CAAM,YAAY,MAAA,KAAW,IAAA;AACtC;AAEO,SAAS,wBAAwB,KAAA,EAA4B;AAClE,EAAA,OAAO,KAAA,CAAM,YAAY,gBAAA,KAAqB,IAAA;AAChD;AAEO,SAAS,wBAAwB,KAAA,EAAwC;AAC9E,EAAA,OAAO,KAAA,EAAO,YAAY,gBAAA,KAAqB,IAAA;AACjD;AA6DO,SAAS,mBAAA,CACd,OACA,SAAA,EACS;AACT,EAAA,IAAI,KAAA,EAAO,eAAe,SAAA,IAAa,CAAC,MAAM,GAAA,CAAI,SAAS,GAAG,OAAO,KAAA;AACrE,EAAA,OAAO,cAAc,QAAA,IAAY,KAAA,CAAM,WAAW,YAAA,IAAgB,OAAA,CAAQ,MAAM,WAAW,CAAA;AAC7F;AAEO,SAAS,iBAAiB,KAAA,EAAwC;AACvE,EAAA,OAAO,mBAAA,CAAoB,OAAO,QAAQ,CAAA;AAC5C;;;AC1KO,SAAS,6BAA6B,KAAA,EAAoD;AAC/F,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,KAAA,CAAM,IAAA;AACvB,EAAA,MAAM,gBAAA,GAAmB,IAAA,KAAS,SAAA,IAAa,KAAA,CAAM,OAAO,IAAA,KAAS,WAAA;AACrE,EAAA,OAAO;AAAA,IACL,UAAU,IAAA,KAAS,MAAA;AAAA,IACnB,aAAA,EAAe,SAAS,MAAA,IAAU,gBAAA;AAAA,IAClC,gBAAA,EAAkB,gBAAA;AAAA,IAClB,kBAAkB,IAAA,KAAS,SAAA;AAAA,IAC3B,mBAAmB,IAAA,KAAS;AAAA,GAC9B;AACF;AAEO,SAAS,8BACd,KAAA,EACwB;AACxB,EAAA,MAAM,IAAA,GAAO,6BAA6B,KAAK,CAAA;AAC/C,EAAA,MAAM,EAAE,OAAM,GAAI,KAAA;AAClB,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,gBAAA,EAAkB,iBAAiB,KAAK,CAAA;AAAA,IACxC,gBAAA,EAAkB,IAAA,CAAK,gBAAA,IAAoB,mBAAA,CAAoB,OAAO,QAAQ,CAAA;AAAA,IAC9E,gBAAA,EAAkB,IAAA,CAAK,gBAAA,IAAoB,mBAAA,CAAoB,OAAO,QAAQ;AAAA,GAChF;AACA,EAAA,IAAI,KAAK,QAAA,EAAU;AACjB,IAAA,MAAM,SAAA,GAAY,wBAAwB,KAAK,CAAA;AAC/C,IAAA,OAAO;AAAA,MACL,YAAA,EAAc,sBAAsB,KAAK,CAAA;AAAA,MACzC,cAAA,EAAgB,SAAA;AAAA,MAChB,mBAAA,EAAqB,SAAA;AAAA,MACrB,mBAAA,EAAqB,wBAAwB,KAAK,CAAA;AAAA,MAClD,GAAG;AAAA,KACL;AAAA,EACF;AACA,EAAA,IAAI,oBAAA,CAAqB,KAAK,CAAA,EAAG;AAC/B,IAAA,MAAM,SAAA,GAAY,wBAAwB,KAAK,CAAA;AAC/C,IAAA,MAAM,SAAA,GAAY,wBAAwB,KAAK,CAAA;AAC/C,IAAA,OAAO;AAAA,MACL,YAAA,EAAc,KAAA;AAAA,MACd,gBAAgB,SAAA,IAAa,SAAA;AAAA,MAC7B,mBAAA,EAAqB,SAAA;AAAA,MACrB,mBAAA,EAAqB,SAAA;AAAA,MACrB,GAAG;AAAA,KACL;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,IAAA;AAAA,IACd,cAAA,EAAgB,IAAA;AAAA,IAChB,mBAAA,EAAqB,IAAA;AAAA,IACrB,mBAAA,EAAqB,iBAAiB,KAAK,CAAA;AAAA,IAC3C,GAAG;AAAA,GACL;AACF;AAEO,SAAS,yBACd,KAAA,EACmB;AACnB,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,6BAA6B,KAAK,CAAA;AAAA,IACxC,KAAA,EAAO,KAAA,CAAM,KAAA,GACT,6BAAA,CAA8B,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA,GAC9D;AAAA,GACN;AACF;AAEO,SAAS,mBACd,KAAA,EACc;AACd,EAAA,OAAO,MAAM,MAAA,CAAO,MAAA;AAAA,IAClB,CAAC,UAAU,6BAAA,CAA8B,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,CAAA,CAAE;AAAA,GAChE;AACF;AAEO,SAAS,sBACd,KAAA,EACc;AACd,EAAA,OAAO,MAAM,MAAA,CAAO,MAAA;AAAA,IAClB,CAAC,UAAU,6BAAA,CAA8B,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,CAAA,CAAE;AAAA,GAChE;AACF","file":"shareGuestPolicy.js","sourcesContent":["import { assertProposalAccess, type ProposalAccess } from \"./proposePolicy.ts\";\nimport { refFieldsForTable } from \"./refGraph.ts\";\nimport type { Manifest } from \"./schemaTypes.ts\";\nimport type {\n ShareInviteKind,\n ShareLinkType,\n ShareOperation,\n ShareScope,\n ShareScopeConflictPatch,\n ShareWizardReferenceInput,\n} from \"./shareTypes.ts\";\n\nfunction inferLegacyPrimary(scope: ShareScope): boolean {\n const access = scope.formAccess;\n return !(access && !access.submit && (access.searchReferences || access.createReferences));\n}\n\nexport function normalizeShareScopes(scopes: ShareScope[]): ShareScope[] {\n const tables = new Set<string>();\n return scopes.map((scope) => {\n const table = scope.table.trim();\n if (!table) throw new Error(\"Every share scope needs a table\");\n if (tables.has(table)) throw new Error(`Duplicate share scope for ${table}`);\n tables.add(table);\n\n const recordIds = scope.recordIds\n ? [...new Set(scope.recordIds.map((id) => id.trim()).filter(Boolean))]\n : undefined;\n const allowedFields = scope.allowedFields\n ? [...new Set(scope.allowedFields.map((field) => field.trim()).filter(Boolean))]\n : undefined;\n\n if (scope.access === \"referenced\" && recordIds) {\n throw new Error(`Referenced scope ${table} cannot specify recordIds`);\n }\n if (recordIds && scope.permission === \"propose\" && scope.ops.insert) {\n throw new Error(`Record-picked scope ${table} cannot allow inserts`);\n }\n if (\n scope.allowCreate &&\n (scope.access !== \"referenced\" || scope.permission !== \"propose\" || !scope.ops.insert)\n ) {\n throw new Error(`allowCreate requires referenced propose insert access for ${table}`);\n }\n\n return {\n ...scope,\n table,\n primary: scope.primary ?? inferLegacyPrimary(scope),\n recordIds,\n allowedFields,\n ops:\n scope.permission === \"view\"\n ? { insert: false, update: false, delete: false }\n : { ...scope.ops },\n };\n });\n}\n\nexport function isReferenceOnlyScope(scope: ShareScope): boolean {\n return scope.primary === false;\n}\n\nexport function mergeReferenceScopes(\n scopes: ShareScope[],\n references: ShareWizardReferenceInput[],\n): ShareScope[] {\n const next = normalizeShareScopes(scopes);\n for (const reference of references) {\n const existing = next.find((scope) => scope.table === reference.table.trim());\n const formAccess = {\n submit: existing?.formAccess?.submit ?? false,\n searchReferences:\n (existing?.formAccess?.searchReferences ?? false) || reference.searchReferences,\n createReferences:\n (existing?.formAccess?.createReferences ?? false) || reference.createReferences,\n };\n if (existing) {\n existing.formAccess = formAccess;\n if (formAccess.createReferences && !existing.primary) {\n existing.permission = \"propose\";\n existing.ops = { insert: true, update: false, delete: false };\n }\n continue;\n }\n next.push({\n table: reference.table,\n primary: false,\n access: \"full\",\n permission: formAccess.createReferences ? \"propose\" : \"view\",\n ops: formAccess.createReferences\n ? { insert: true, update: false, delete: false }\n : { insert: false, update: false, delete: false },\n formAccess,\n });\n }\n return next;\n}\n\nfunction isInsertOnlyScope(scope: ShareScope): boolean {\n return (\n scope.permission === \"propose\" && scope.ops.insert && !scope.ops.update && !scope.ops.delete\n );\n}\n\nexport function isFormSubmissionScope(scope: ShareScope): boolean {\n return scope.formAccess?.submit === true;\n}\n\nexport function canSearchFormReferences(scope: ShareScope): boolean {\n return scope.formAccess?.searchReferences === true;\n}\n\nexport function canCreateFormReferences(scope: ShareScope | undefined): boolean {\n return scope?.formAccess?.createReferences === true;\n}\n\nexport function assertShareLinkTypeConsistency(\n type: ShareLinkType,\n scopes: ShareScope[],\n inviteKind?: ShareInviteKind,\n): void {\n const normalized = normalizeShareScopes(scopes);\n if (normalized.length === 0) throw new Error(\"Add at least one table scope\");\n if (type === \"propose\" && inviteKind === \"public\") {\n throw new Error(\"Proposal shares require a recipient invite\");\n }\n for (const scope of normalized) {\n if (type === \"view\" && scope.permission !== \"view\") {\n throw new Error(`View shares require read-only access for ${scope.table}`);\n }\n if (\n type === \"propose\" &&\n scope.permission !== \"propose\" &&\n (!isReferenceOnlyScope(scope) || scope.permission !== \"view\")\n ) {\n throw new Error(`Proposal shares require proposal access for ${scope.table}`);\n }\n if (type === \"propose\" && scope.formAccess?.submit) {\n throw new Error(`Proposal shares cannot use form submission scopes for ${scope.table}`);\n }\n if (type === \"propose\" && isReferenceOnlyScope(scope)) {\n const formAccess = scope.formAccess;\n if (!formAccess) throw new Error(`Reference scope ${scope.table} needs reference access`);\n if (scope.access !== \"full\") {\n throw new Error(`Reference picker scopes must use full-table access for ${scope.table}`);\n }\n if (formAccess.createReferences && !isInsertOnlyScope(scope)) {\n throw new Error(`Reference creation requires insert-only access for ${scope.table}`);\n }\n if (!formAccess.createReferences && scope.permission !== \"view\") {\n throw new Error(`Read-only reference picker access is required for ${scope.table}`);\n }\n }\n if (type === \"form\") assertFormScope(scope);\n }\n}\n\nfunction assertFormScope(scope: ShareScope): void {\n const access = scope.formAccess;\n if (!access) throw new Error(`Form shares require explicit form access for ${scope.table}`);\n if (!access.submit && !access.searchReferences && !access.createReferences) {\n throw new Error(`Form scope ${scope.table} does not grant any capability`);\n }\n if (scope.access !== \"full\")\n throw new Error(`Form scopes must use full-table access for ${scope.table}`);\n if ((access.submit || access.createReferences) && !isInsertOnlyScope(scope)) {\n throw new Error(\n `Form submission and reference creation require insert-only access for ${scope.table}`,\n );\n }\n if (!access.submit && !access.createReferences && scope.permission !== \"view\") {\n throw new Error(`Read-only form reference access is required for ${scope.table}`);\n }\n}\n\nexport function canProposeOperation(\n scope: ShareScope | undefined,\n operation: ShareOperation,\n): boolean {\n if (scope?.permission !== \"propose\" || !scope.ops[operation]) return false;\n return operation !== \"insert\" || scope.access !== \"referenced\" || Boolean(scope.allowCreate);\n}\n\nexport function canProposeInsert(scope: ShareScope | undefined): boolean {\n return canProposeOperation(scope, \"insert\");\n}\n\nexport function eligibleReferencedParents(\n manifest: Manifest,\n scopes: ShareScope[],\n targetTable: string,\n): ShareScope[] {\n return scopes.filter((scope) => {\n const table = manifest.tables.find((candidate) => candidate.name === scope.table);\n return Boolean(\n table &&\n table.name !== targetTable &&\n refFieldsForTable(table).some((edge) => edge.targetTable === targetTable),\n );\n });\n}\n\nexport function assertShareOperation(\n scopes: ShareScope[],\n table: string,\n operation: ShareOperation,\n rowId?: string,\n fields?: string[],\n): ShareScope {\n const scope = scopes.find((candidate) => candidate.table === table);\n if (scope?.permission !== \"propose\") throw new Error(`${operation} is not allowed for ${table}`);\n const access = {\n table: scope.table,\n ops: scope.ops,\n recordIds: scope.recordIds,\n allowedFields: scope.allowedFields,\n } satisfies ProposalAccess;\n assertProposalAccess([access], { table, operation, rowId, fields });\n if (operation === \"insert\" && scope.access === \"referenced\" && !scope.allowCreate) {\n throw new Error(`Creating referenced ${table} rows is not allowed`);\n }\n return scope;\n}\n\nexport function findShareScopeConflicts(\n scopes: ShareScope[],\n patches: ShareScopeConflictPatch[],\n): ShareScopeConflictPatch[] {\n return patches.filter((patch) => {\n try {\n assertShareOperation(\n scopes,\n patch.table,\n patch.op,\n patch.rowId.startsWith(\"new:\") ? undefined : patch.rowId,\n Object.keys(patch.fields),\n );\n return false;\n } catch {\n return true;\n }\n });\n}\n","import {\n canCreateFormReferences,\n canProposeInsert,\n canProposeOperation,\n canSearchFormReferences,\n isFormSubmissionScope,\n isReferenceOnlyScope,\n} from \"./shareScopes.ts\";\nimport type {\n GuestCapabilities,\n GuestCapabilityInput,\n GuestLinkCapabilities,\n GuestScopeCapabilities,\n ShareScope,\n} from \"./shareTypes.ts\";\n\nexport function resolveGuestLinkCapabilities(input: GuestCapabilityInput): GuestLinkCapabilities {\n const { type } = input.link;\n const recipientPropose = type === \"propose\" && input.invite.kind === \"recipient\";\n return {\n formMode: type === \"form\",\n canBrowseRows: type === \"view\" || recipientPropose,\n canListProposals: recipientPropose,\n canUpsertPatches: type === \"propose\",\n canToggleViewMode: type !== \"form\",\n };\n}\n\nexport function resolveGuestScopeCapabilities(\n input: GuestCapabilityInput & { scope: ShareScope },\n): GuestScopeCapabilities {\n const link = resolveGuestLinkCapabilities(input);\n const { scope } = input;\n const propose = {\n canProposeInsert: canProposeInsert(scope),\n canProposeUpdate: link.canUpsertPatches && canProposeOperation(scope, \"update\"),\n canProposeDelete: link.canUpsertPatches && canProposeOperation(scope, \"delete\"),\n };\n if (link.formMode) {\n const canSearch = canSearchFormReferences(scope);\n return {\n visibleInNav: isFormSubmissionScope(scope),\n canReadDisplay: canSearch,\n canSearchReferences: canSearch,\n canCreateReferences: canCreateFormReferences(scope),\n ...propose,\n };\n }\n if (isReferenceOnlyScope(scope)) {\n const canSearch = canSearchFormReferences(scope);\n const canCreate = canCreateFormReferences(scope);\n return {\n visibleInNav: false,\n canReadDisplay: canSearch || canCreate,\n canSearchReferences: canSearch,\n canCreateReferences: canCreate,\n ...propose,\n };\n }\n return {\n visibleInNav: true,\n canReadDisplay: true,\n canSearchReferences: true,\n canCreateReferences: canProposeInsert(scope),\n ...propose,\n };\n}\n\nexport function resolveGuestCapabilities(\n input: GuestCapabilityInput & { scope?: ShareScope },\n): GuestCapabilities {\n return {\n link: resolveGuestLinkCapabilities(input),\n scope: input.scope\n ? resolveGuestScopeCapabilities({ ...input, scope: input.scope })\n : undefined,\n };\n}\n\nexport function guestVisibleScopes(\n input: GuestCapabilityInput & { scopes: ShareScope[] },\n): ShareScope[] {\n return input.scopes.filter(\n (scope) => resolveGuestScopeCapabilities({ ...input, scope }).visibleInNav,\n );\n}\n\nexport function guestSearchableScopes(\n input: GuestCapabilityInput & { scopes: ShareScope[] },\n): ShareScope[] {\n return input.scopes.filter(\n (scope) => resolveGuestScopeCapabilities({ ...input, scope }).canSearchReferences,\n );\n}\n"]}
@@ -0,0 +1,113 @@
1
+ import { type Manifest } from "./schemaTypes.ts";
2
+ import type { ShareScope } from "./shareTypes.ts";
3
+ export type ProposalPatchForPlanning = {
4
+ id: string;
5
+ table: string;
6
+ rowId: string;
7
+ op: "insert" | "update" | "delete";
8
+ fields: Record<string, unknown>;
9
+ seq: number;
10
+ _creationTime: number;
11
+ };
12
+ /**
13
+ * Mint a provisional row id. The `new:` prefix is what every guard in this
14
+ * module keys off, so producers must go through here rather than inlining it.
15
+ */
16
+ export declare function newProvisionalRowId(): string;
17
+ /** Table-qualified key for a provisional row, avoiding cross-table `new:*` collisions. */
18
+ export declare function provisionalRowKey(table: string, rowId: string): string;
19
+ /**
20
+ * Validate and order proposal patches so every provisional producer is applied
21
+ * before patches that reference it. Stored sequence remains a stable tie-breaker.
22
+ */
23
+ export declare function planProposalPatches<P extends ProposalPatchForPlanning>(manifest: Manifest, patches: readonly P[]): {
24
+ orderedPatches: P[];
25
+ hasDependencies: boolean;
26
+ };
27
+ /** Strip stale fields, skip empty updates, fail closed on type/requiredness errors. */
28
+ export declare function prepareProposalPatchesForAccept<P extends ProposalPatchForPlanning>(manifest: Manifest, scopes: readonly ShareScope[], patches: readonly P[]): P[];
29
+ export type ProposalPatchTarget = {
30
+ table: string;
31
+ rowId: string;
32
+ op: "insert" | "update" | "delete";
33
+ /** Present for updates; key set used for field-aware overlap. */
34
+ fields?: Record<string, unknown>;
35
+ };
36
+ export type ProposalStaleCheckPatch = ProposalPatchTarget & {
37
+ baseFields?: Record<string, unknown> | null;
38
+ };
39
+ export type ProposalPatchTargets = {
40
+ id: string;
41
+ patches: ProposalPatchTarget[];
42
+ };
43
+ /**
44
+ * Base snapshot captured server-side at propose time (plan 051 PR 2); accept
45
+ * compares it against the live row. Proposals without base data (share links,
46
+ * `col_` API tokens) are exempt so existing flows keep today's behavior.
47
+ */
48
+ export declare function snapshotBaseFields(op: "insert" | "update" | "delete", proposedFields: Record<string, unknown>, liveRow: Record<string, unknown> | null): Record<string, unknown> | undefined;
49
+ export declare function patchIsStale(patch: ProposalStaleCheckPatch, liveRow: Record<string, unknown> | null): {
50
+ stale: boolean;
51
+ reason?: string;
52
+ };
53
+ /**
54
+ * Ids of proposals with a true field-level conflict against another listed
55
+ * proposal: intersecting update fields, or delete vs any op, on the same live
56
+ * row. Disjoint updates and provisional `new:` rows do not conflict.
57
+ */
58
+ export declare function findOverlappingProposalIds(proposals: ProposalPatchTargets[]): Set<string>;
59
+ /**
60
+ * Ids among `candidates` that field-conflict with `target` (excludes target itself).
61
+ * Prefer this over all-pairs when answering a single-proposal question (e.g. publish mine).
62
+ */
63
+ export declare function findProposalIdsOverlappingTarget(target: ProposalPatchTargets, candidates: ProposalPatchTargets[]): Set<string>;
64
+ /** True when `target` field-conflicts with any other candidate (short-circuits). */
65
+ export declare function proposalOverlapsAny(target: ProposalPatchTargets, candidates: ProposalPatchTargets[]): boolean;
66
+ /** Replace provisional IDs only in manifest-declared top-level `id` and `id[]` fields. */
67
+ export declare function replaceProvisionalReferences(manifest: Manifest, tableName: string, fields: Record<string, unknown>, createdIds: ReadonlyMap<string, string>): Record<string, unknown>;
68
+ export type ProposalPatchOp = "insert" | "update" | "delete";
69
+ /** Editor/CMS draft patch — rowId is always known (including `new:…`). */
70
+ export type ProposalDraftPatch = {
71
+ table: string;
72
+ rowId: string;
73
+ op: ProposalPatchOp;
74
+ fields: Record<string, unknown>;
75
+ };
76
+ export type ProposalClientPatch = ProposalDraftPatch & {
77
+ id?: string;
78
+ baseFields?: Record<string, unknown> | null;
79
+ baseline?: Record<string, unknown> | null;
80
+ seq?: number;
81
+ };
82
+ /** Client-facing proposal summary (CMS + profile proposal list). */
83
+ export type ProposalClientSummary = {
84
+ id: string;
85
+ source: "share" | "api" | "editor";
86
+ status: string;
87
+ apiProfileId?: string;
88
+ createdByUserId?: string;
89
+ createdAt: number;
90
+ patches: ProposalClientPatch[];
91
+ };
92
+ export type ProposalBatchOutcome = {
93
+ id: string;
94
+ outcome: "accepted" | "rejected" | "skipped_stale" | "skipped_overlap";
95
+ message?: string;
96
+ };
97
+ export declare const OVERLAP_MESSAGE = "Another pending proposal touches the same rows";
98
+ /**
99
+ * Accept-batch orchestration shared by host profile accept and the CMS fixture
100
+ * mock store. Overlap only gates multi-accept; stale always skips. `onAccept`
101
+ * runs only for proposals that clear those gates.
102
+ */
103
+ export declare function runAcceptProposalBatch(args: {
104
+ proposalIds: string[];
105
+ activeProposals: Array<{
106
+ id: string;
107
+ patches: ProposalStaleCheckPatch[];
108
+ }>;
109
+ getLiveRow: (table: string, rowId: string) => Record<string, unknown> | null | Promise<Record<string, unknown> | null>;
110
+ onAccept: (id: string) => void | Promise<void>;
111
+ /** How to treat ids absent from `activeProposals`. Default: throw. */
112
+ onMissing?: "reject" | "throw";
113
+ }): Promise<ProposalBatchOutcome[]>;